(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  typeof define === 'function' && define.amd ? define(factory) :
  (global = global || self, global.logictopo = factory());
}(this, (function () { 'use strict';

  function _classCallCheck(instance, Constructor) {
    if (!(instance instanceof Constructor)) {
      throw new TypeError("Cannot call a class as a function");
    }
  }

  function _defineProperties(target, props) {
    for (var i = 0; i < props.length; i++) {
      var descriptor = props[i];
      descriptor.enumerable = descriptor.enumerable || false;
      descriptor.configurable = true;
      if ("value" in descriptor) descriptor.writable = true;
      Object.defineProperty(target, descriptor.key, descriptor);
    }
  }

  function _createClass(Constructor, protoProps, staticProps) {
    if (protoProps) _defineProperties(Constructor.prototype, protoProps);
    if (staticProps) _defineProperties(Constructor, staticProps);
    return Constructor;
  }

  function _defineProperty(obj, key, value) {
    if (key in obj) {
      Object.defineProperty(obj, key, {
        value: value,
        enumerable: true,
        configurable: true,
        writable: true
      });
    } else {
      obj[key] = value;
    }

    return obj;
  }

  function ownKeys(object, enumerableOnly) {
    var keys = Object.keys(object);

    if (Object.getOwnPropertySymbols) {
      var symbols = Object.getOwnPropertySymbols(object);
      if (enumerableOnly) symbols = symbols.filter(function (sym) {
        return Object.getOwnPropertyDescriptor(object, sym).enumerable;
      });
      keys.push.apply(keys, symbols);
    }

    return keys;
  }

  function _objectSpread2(target) {
    for (var i = 1; i < arguments.length; i++) {
      var source = arguments[i] != null ? arguments[i] : {};

      if (i % 2) {
        ownKeys(Object(source), true).forEach(function (key) {
          _defineProperty(target, key, source[key]);
        });
      } else if (Object.getOwnPropertyDescriptors) {
        Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
      } else {
        ownKeys(Object(source)).forEach(function (key) {
          Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
        });
      }
    }

    return target;
  }

  function _inherits(subClass, superClass) {
    if (typeof superClass !== "function" && superClass !== null) {
      throw new TypeError("Super expression must either be null or a function");
    }

    subClass.prototype = Object.create(superClass && superClass.prototype, {
      constructor: {
        value: subClass,
        writable: true,
        configurable: true
      }
    });
    if (superClass) _setPrototypeOf(subClass, superClass);
  }

  function _getPrototypeOf(o) {
    _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
      return o.__proto__ || Object.getPrototypeOf(o);
    };
    return _getPrototypeOf(o);
  }

  function _setPrototypeOf(o, p) {
    _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
      o.__proto__ = p;
      return o;
    };

    return _setPrototypeOf(o, p);
  }

  function _isNativeReflectConstruct() {
    if (typeof Reflect === "undefined" || !Reflect.construct) return false;
    if (Reflect.construct.sham) return false;
    if (typeof Proxy === "function") return true;

    try {
      Date.prototype.toString.call(Reflect.construct(Date, [], function () {}));
      return true;
    } catch (e) {
      return false;
    }
  }

  function _assertThisInitialized(self) {
    if (self === void 0) {
      throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
    }

    return self;
  }

  function _possibleConstructorReturn(self, call) {
    if (call && (typeof call === "object" || typeof call === "function")) {
      return call;
    }

    return _assertThisInitialized(self);
  }

  function _createSuper(Derived) {
    return function () {
      var Super = _getPrototypeOf(Derived),
          result;

      if (_isNativeReflectConstruct()) {
        var NewTarget = _getPrototypeOf(this).constructor;

        result = Reflect.construct(Super, arguments, NewTarget);
      } else {
        result = Super.apply(this, arguments);
      }

      return _possibleConstructorReturn(this, result);
    };
  }

  function _unsupportedIterableToArray(o, minLen) {
    if (!o) return;
    if (typeof o === "string") return _arrayLikeToArray(o, minLen);
    var n = Object.prototype.toString.call(o).slice(8, -1);
    if (n === "Object" && o.constructor) n = o.constructor.name;
    if (n === "Map" || n === "Set") return Array.from(n);
    if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
  }

  function _arrayLikeToArray(arr, len) {
    if (len == null || len > arr.length) len = arr.length;

    for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];

    return arr2;
  }

  function _createForOfIteratorHelper(o) {
    if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) {
      if (Array.isArray(o) || (o = _unsupportedIterableToArray(o))) {
        var i = 0;

        var F = function () {};

        return {
          s: F,
          n: function () {
            if (i >= o.length) return {
              done: true
            };
            return {
              done: false,
              value: o[i++]
            };
          },
          e: function (e) {
            throw e;
          },
          f: F
        };
      }

      throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
    }

    var it,
        normalCompletion = true,
        didErr = false,
        err;
    return {
      s: function () {
        it = o[Symbol.iterator]();
      },
      n: function () {
        var step = it.next();
        normalCompletion = step.done;
        return step;
      },
      e: function (e) {
        didErr = true;
        err = e;
      },
      f: function () {
        try {
          if (!normalCompletion && it.return != null) it.return();
        } finally {
          if (didErr) throw err;
        }
      }
    };
  }

  function createCommonjsModule(fn, module) {
  	return module = { exports: {} }, fn(module, module.exports), module.exports;
  }

  var _global = createCommonjsModule(function (module) {
  // https://github.com/zloirock/core-js/issues/86#issuecomment-115759028
  var global = module.exports = typeof window != 'undefined' && window.Math == Math
    ? window : typeof self != 'undefined' && self.Math == Math ? self
    // eslint-disable-next-line no-new-func
    : Function('return this')();
  if (typeof __g == 'number') __g = global; // eslint-disable-line no-undef
  });

  var _core = createCommonjsModule(function (module) {
  var core = module.exports = { version: '2.6.11' };
  if (typeof __e == 'number') __e = core; // eslint-disable-line no-undef
  });
  var _core_1 = _core.version;

  var _isObject = function (it) {
    return typeof it === 'object' ? it !== null : typeof it === 'function';
  };

  var _anObject = function (it) {
    if (!_isObject(it)) throw TypeError(it + ' is not an object!');
    return it;
  };

  var _fails = function (exec) {
    try {
      return !!exec();
    } catch (e) {
      return true;
    }
  };

  // Thank's IE8 for his funny defineProperty
  var _descriptors = !_fails(function () {
    return Object.defineProperty({}, 'a', { get: function () { return 7; } }).a != 7;
  });

  var document$1 = _global.document;
  // typeof document.createElement is 'object' in old IE
  var is = _isObject(document$1) && _isObject(document$1.createElement);
  var _domCreate = function (it) {
    return is ? document$1.createElement(it) : {};
  };

  var _ie8DomDefine = !_descriptors && !_fails(function () {
    return Object.defineProperty(_domCreate('div'), 'a', { get: function () { return 7; } }).a != 7;
  });

  // 7.1.1 ToPrimitive(input [, PreferredType])

  // instead of the ES6 spec version, we didn't implement @@toPrimitive case
  // and the second argument - flag - preferred type is a string
  var _toPrimitive = function (it, S) {
    if (!_isObject(it)) return it;
    var fn, val;
    if (S && typeof (fn = it.toString) == 'function' && !_isObject(val = fn.call(it))) return val;
    if (typeof (fn = it.valueOf) == 'function' && !_isObject(val = fn.call(it))) return val;
    if (!S && typeof (fn = it.toString) == 'function' && !_isObject(val = fn.call(it))) return val;
    throw TypeError("Can't convert object to primitive value");
  };

  var dP = Object.defineProperty;

  var f = _descriptors ? Object.defineProperty : function defineProperty(O, P, Attributes) {
    _anObject(O);
    P = _toPrimitive(P, true);
    _anObject(Attributes);
    if (_ie8DomDefine) try {
      return dP(O, P, Attributes);
    } catch (e) { /* empty */ }
    if ('get' in Attributes || 'set' in Attributes) throw TypeError('Accessors not supported!');
    if ('value' in Attributes) O[P] = Attributes.value;
    return O;
  };

  var _objectDp = {
  	f: f
  };

  var _propertyDesc = function (bitmap, value) {
    return {
      enumerable: !(bitmap & 1),
      configurable: !(bitmap & 2),
      writable: !(bitmap & 4),
      value: value
    };
  };

  var _hide = _descriptors ? function (object, key, value) {
    return _objectDp.f(object, key, _propertyDesc(1, value));
  } : function (object, key, value) {
    object[key] = value;
    return object;
  };

  var hasOwnProperty = {}.hasOwnProperty;
  var _has = function (it, key) {
    return hasOwnProperty.call(it, key);
  };

  var id = 0;
  var px = Math.random();
  var _uid = function (key) {
    return 'Symbol('.concat(key === undefined ? '' : key, ')_', (++id + px).toString(36));
  };

  var _shared = createCommonjsModule(function (module) {
  var SHARED = '__core-js_shared__';
  var store = _global[SHARED] || (_global[SHARED] = {});

  (module.exports = function (key, value) {
    return store[key] || (store[key] = value !== undefined ? value : {});
  })('versions', []).push({
    version: _core.version,
    mode:  'global',
    copyright: '© 2019 Denis Pushkarev (zloirock.ru)'
  });
  });

  var _functionToString = _shared('native-function-to-string', Function.toString);

  var _redefine = createCommonjsModule(function (module) {
  var SRC = _uid('src');

  var TO_STRING = 'toString';
  var TPL = ('' + _functionToString).split(TO_STRING);

  _core.inspectSource = function (it) {
    return _functionToString.call(it);
  };

  (module.exports = function (O, key, val, safe) {
    var isFunction = typeof val == 'function';
    if (isFunction) _has(val, 'name') || _hide(val, 'name', key);
    if (O[key] === val) return;
    if (isFunction) _has(val, SRC) || _hide(val, SRC, O[key] ? '' + O[key] : TPL.join(String(key)));
    if (O === _global) {
      O[key] = val;
    } else if (!safe) {
      delete O[key];
      _hide(O, key, val);
    } else if (O[key]) {
      O[key] = val;
    } else {
      _hide(O, key, val);
    }
  // add fake Function#toString for correct work wrapped methods / constructors with methods like LoDash isNative
  })(Function.prototype, TO_STRING, function toString() {
    return typeof this == 'function' && this[SRC] || _functionToString.call(this);
  });
  });

  var _aFunction = function (it) {
    if (typeof it != 'function') throw TypeError(it + ' is not a function!');
    return it;
  };

  // optional / simple context binding

  var _ctx = function (fn, that, length) {
    _aFunction(fn);
    if (that === undefined) return fn;
    switch (length) {
      case 1: return function (a) {
        return fn.call(that, a);
      };
      case 2: return function (a, b) {
        return fn.call(that, a, b);
      };
      case 3: return function (a, b, c) {
        return fn.call(that, a, b, c);
      };
    }
    return function (/* ...args */) {
      return fn.apply(that, arguments);
    };
  };

  var PROTOTYPE = 'prototype';

  var $export = function (type, name, source) {
    var IS_FORCED = type & $export.F;
    var IS_GLOBAL = type & $export.G;
    var IS_STATIC = type & $export.S;
    var IS_PROTO = type & $export.P;
    var IS_BIND = type & $export.B;
    var target = IS_GLOBAL ? _global : IS_STATIC ? _global[name] || (_global[name] = {}) : (_global[name] || {})[PROTOTYPE];
    var exports = IS_GLOBAL ? _core : _core[name] || (_core[name] = {});
    var expProto = exports[PROTOTYPE] || (exports[PROTOTYPE] = {});
    var key, own, out, exp;
    if (IS_GLOBAL) source = name;
    for (key in source) {
      // contains in native
      own = !IS_FORCED && target && target[key] !== undefined;
      // export native or passed
      out = (own ? target : source)[key];
      // bind timers to global for call from export context
      exp = IS_BIND && own ? _ctx(out, _global) : IS_PROTO && typeof out == 'function' ? _ctx(Function.call, out) : out;
      // extend global
      if (target) _redefine(target, key, out, type & $export.U);
      // export
      if (exports[key] != out) _hide(exports, key, exp);
      if (IS_PROTO && expProto[key] != out) expProto[key] = out;
    }
  };
  _global.core = _core;
  // type bitmap
  $export.F = 1;   // forced
  $export.G = 2;   // global
  $export.S = 4;   // static
  $export.P = 8;   // proto
  $export.B = 16;  // bind
  $export.W = 32;  // wrap
  $export.U = 64;  // safe
  $export.R = 128; // real proto method for `library`
  var _export = $export;

  var toString = {}.toString;

  var _cof = function (it) {
    return toString.call(it).slice(8, -1);
  };

  // fallback for non-array-like ES3 and non-enumerable old V8 strings

  // eslint-disable-next-line no-prototype-builtins
  var _iobject = Object('z').propertyIsEnumerable(0) ? Object : function (it) {
    return _cof(it) == 'String' ? it.split('') : Object(it);
  };

  // 7.2.1 RequireObjectCoercible(argument)
  var _defined = function (it) {
    if (it == undefined) throw TypeError("Can't call method on  " + it);
    return it;
  };

  // 7.1.13 ToObject(argument)

  var _toObject = function (it) {
    return Object(_defined(it));
  };

  // 7.1.4 ToInteger
  var ceil = Math.ceil;
  var floor = Math.floor;
  var _toInteger = function (it) {
    return isNaN(it = +it) ? 0 : (it > 0 ? floor : ceil)(it);
  };

  // 7.1.15 ToLength

  var min = Math.min;
  var _toLength = function (it) {
    return it > 0 ? min(_toInteger(it), 0x1fffffffffffff) : 0; // pow(2, 53) - 1 == 9007199254740991
  };

  // 7.2.2 IsArray(argument)

  var _isArray = Array.isArray || function isArray(arg) {
    return _cof(arg) == 'Array';
  };

  var _wks = createCommonjsModule(function (module) {
  var store = _shared('wks');

  var Symbol = _global.Symbol;
  var USE_SYMBOL = typeof Symbol == 'function';

  var $exports = module.exports = function (name) {
    return store[name] || (store[name] =
      USE_SYMBOL && Symbol[name] || (USE_SYMBOL ? Symbol : _uid)('Symbol.' + name));
  };

  $exports.store = store;
  });

  var SPECIES = _wks('species');

  var _arraySpeciesConstructor = function (original) {
    var C;
    if (_isArray(original)) {
      C = original.constructor;
      // cross-realm fallback
      if (typeof C == 'function' && (C === Array || _isArray(C.prototype))) C = undefined;
      if (_isObject(C)) {
        C = C[SPECIES];
        if (C === null) C = undefined;
      }
    } return C === undefined ? Array : C;
  };

  // 9.4.2.3 ArraySpeciesCreate(originalArray, length)


  var _arraySpeciesCreate = function (original, length) {
    return new (_arraySpeciesConstructor(original))(length);
  };

  // 0 -> Array#forEach
  // 1 -> Array#map
  // 2 -> Array#filter
  // 3 -> Array#some
  // 4 -> Array#every
  // 5 -> Array#find
  // 6 -> Array#findIndex





  var _arrayMethods = function (TYPE, $create) {
    var IS_MAP = TYPE == 1;
    var IS_FILTER = TYPE == 2;
    var IS_SOME = TYPE == 3;
    var IS_EVERY = TYPE == 4;
    var IS_FIND_INDEX = TYPE == 6;
    var NO_HOLES = TYPE == 5 || IS_FIND_INDEX;
    var create = $create || _arraySpeciesCreate;
    return function ($this, callbackfn, that) {
      var O = _toObject($this);
      var self = _iobject(O);
      var f = _ctx(callbackfn, that, 3);
      var length = _toLength(self.length);
      var index = 0;
      var result = IS_MAP ? create($this, length) : IS_FILTER ? create($this, 0) : undefined;
      var val, res;
      for (;length > index; index++) if (NO_HOLES || index in self) {
        val = self[index];
        res = f(val, index, O);
        if (TYPE) {
          if (IS_MAP) result[index] = res;   // map
          else if (res) switch (TYPE) {
            case 3: return true;             // some
            case 5: return val;              // find
            case 6: return index;            // findIndex
            case 2: result.push(val);        // filter
          } else if (IS_EVERY) return false; // every
        }
      }
      return IS_FIND_INDEX ? -1 : IS_SOME || IS_EVERY ? IS_EVERY : result;
    };
  };

  var _strictMethod = function (method, arg) {
    return !!method && _fails(function () {
      // eslint-disable-next-line no-useless-call
      arg ? method.call(null, function () { /* empty */ }, 1) : method.call(null);
    });
  };

  var $forEach = _arrayMethods(0);
  var STRICT = _strictMethod([].forEach, true);

  _export(_export.P + _export.F * !STRICT, 'Array', {
    // 22.1.3.10 / 15.4.4.18 Array.prototype.forEach(callbackfn [, thisArg])
    forEach: function forEach(callbackfn /* , thisArg */) {
      return $forEach(this, callbackfn, arguments[1]);
    }
  });

  var dP$1 = _objectDp.f;
  var FProto = Function.prototype;
  var nameRE = /^\s*function ([^ (]*)/;
  var NAME = 'name';

  // 19.2.4.2 name
  NAME in FProto || _descriptors && dP$1(FProto, NAME, {
    configurable: true,
    get: function () {
      try {
        return ('' + this).match(nameRE)[1];
      } catch (e) {
        return '';
      }
    }
  });

  /**
   * zrender: 生成唯一id
   *
   * @author errorrik (errorrik@gmail.com)
   */

  var idStart = 0x0907;

  function guid () {
      return idStart++;
  }

  /**
   * @module zrender/core/util
   */

  // 用于处理merge时无法遍历Date等对象的问题
  var BUILTIN_OBJECT = {
      '[object Function]': 1,
      '[object RegExp]': 1,
      '[object Date]': 1,
      '[object Error]': 1,
      '[object CanvasGradient]': 1,
      '[object CanvasPattern]': 1,
      // For node-canvas
      '[object Image]': 1,
      '[object Canvas]': 1
  };

  var TYPED_ARRAY = {
      '[object Int8Array]': 1,
      '[object Uint8Array]': 1,
      '[object Uint8ClampedArray]': 1,
      '[object Int16Array]': 1,
      '[object Uint16Array]': 1,
      '[object Int32Array]': 1,
      '[object Uint32Array]': 1,
      '[object Float32Array]': 1,
      '[object Float64Array]': 1
  };

  var objToString = Object.prototype.toString;

  var arrayProto = Array.prototype;
  var nativeForEach = arrayProto.forEach;
  var nativeSlice = arrayProto.slice;
  var nativeMap = arrayProto.map;

  // Avoid assign to an exported variable, for transforming to cjs.
  var methods = {};

  /**
   * Those data types can be cloned:
   *     Plain object, Array, TypedArray, number, string, null, undefined.
   * Those data types will be assgined using the orginal data:
   *     BUILTIN_OBJECT
   * Instance of user defined class will be cloned to a plain object, without
   * properties in prototype.
   * Other data types is not supported (not sure what will happen).
   *
   * Caution: do not support clone Date, for performance consideration.
   * (There might be a large number of date in `series.data`).
   * So date should not be modified in and out of echarts.
   *
   * @param {*} source
   * @return {*} new
   */
  function clone(source) {
      if (source == null || typeof source !== 'object') {
          return source;
      }

      var result = source;
      var typeStr = objToString.call(source);

      if (typeStr === '[object Array]') {
          if (!isPrimitive(source)) {
              result = [];
              for (var i = 0, len = source.length; i < len; i++) {
                  result[i] = clone(source[i]);
              }
          }
      }
      else if (TYPED_ARRAY[typeStr]) {
          if (!isPrimitive(source)) {
              var Ctor = source.constructor;
              if (source.constructor.from) {
                  result = Ctor.from(source);
              }
              else {
                  result = new Ctor(source.length);
                  for (var i = 0, len = source.length; i < len; i++) {
                      result[i] = clone(source[i]);
                  }
              }
          }
      }
      else if (!BUILTIN_OBJECT[typeStr] && !isPrimitive(source) && !isDom(source)) {
          result = {};
          for (var key in source) {
              if (source.hasOwnProperty(key)) {
                  result[key] = clone(source[key]);
              }
          }
      }

      return result;
  }

  /**
   * @memberOf module:zrender/core/util
   * @param {*} target
   * @param {*} source
   * @param {boolean} [overwrite=false]
   */
  function merge(target, source, overwrite) {
      // We should escapse that source is string
      // and enter for ... in ...
      if (!isObject(source) || !isObject(target)) {
          return overwrite ? clone(source) : target;
      }

      for (var key in source) {
          if (source.hasOwnProperty(key)) {
              var targetProp = target[key];
              var sourceProp = source[key];

              if (isObject(sourceProp)
                  && isObject(targetProp)
                  && !isArray(sourceProp)
                  && !isArray(targetProp)
                  && !isDom(sourceProp)
                  && !isDom(targetProp)
                  && !isBuiltInObject(sourceProp)
                  && !isBuiltInObject(targetProp)
                  && !isPrimitive(sourceProp)
                  && !isPrimitive(targetProp)
              ) {
                  // 如果需要递归覆盖，就递归调用merge
                  merge(targetProp, sourceProp, overwrite);
              }
              else if (overwrite || !(key in target)) {
                  // 否则只处理overwrite为true，或者在目标对象中没有此属性的情况
                  // NOTE，在 target[key] 不存在的时候也是直接覆盖
                  target[key] = clone(source[key]);
              }
          }
      }

      return target;
  }

  /**
   * @param {*} target
   * @param {*} source
   * @memberOf module:zrender/core/util
   */
  function extend(target, source) {
      for (var key in source) {
          if (source.hasOwnProperty(key)) {
              target[key] = source[key];
          }
      }
      return target;
  }

  /**
   * @param {*} target
   * @param {*} source
   * @param {boolean} [overlay=false]
   * @memberOf module:zrender/core/util
   */
  function defaults(target, source, overlay) {
      for (var key in source) {
          if (source.hasOwnProperty(key)
              && (overlay ? source[key] != null : target[key] == null)
          ) {
              target[key] = source[key];
          }
      }
      return target;
  }

  var createCanvas = function () {
      return methods.createCanvas();
  };

  methods.createCanvas = function () {
      return document.createElement('canvas');
  };

  // FIXME
  var _ctx$1;

  function getContext() {
      if (!_ctx$1) {
          // Use util.createCanvas instead of createCanvas
          // because createCanvas may be overwritten in different environment
          _ctx$1 = createCanvas().getContext('2d');
      }
      return _ctx$1;
  }

  /**
   * 查询数组中元素的index
   * @memberOf module:zrender/core/util
   */
  function indexOf(array, value) {
      if (array) {
          if (array.indexOf) {
              return array.indexOf(value);
          }
          for (var i = 0, len = array.length; i < len; i++) {
              if (array[i] === value) {
                  return i;
              }
          }
      }
      return -1;
  }

  /**
   * 构造类继承关系
   *
   * @memberOf module:zrender/core/util
   * @param {Function} clazz 源类
   * @param {Function} baseClazz 基类
   */
  function inherits(clazz, baseClazz) {
      var clazzPrototype = clazz.prototype;
      function F() {}
      F.prototype = baseClazz.prototype;
      clazz.prototype = new F();

      for (var prop in clazzPrototype) {
          clazz.prototype[prop] = clazzPrototype[prop];
      }
      clazz.prototype.constructor = clazz;
      clazz.superClass = baseClazz;
  }

  /**
   * @memberOf module:zrender/core/util
   * @param {Object|Function} target
   * @param {Object|Function} sorce
   * @param {boolean} overlay
   */
  function mixin(target, source, overlay) {
      target = 'prototype' in target ? target.prototype : target;
      source = 'prototype' in source ? source.prototype : source;

      defaults(target, source, overlay);
  }

  /**
   * Consider typed array.
   * @param {Array|TypedArray} data
   */
  function isArrayLike(data) {
      if (!data) {
          return;
      }
      if (typeof data === 'string') {
          return false;
      }
      return typeof data.length === 'number';
  }

  /**
   * 数组或对象遍历
   * @memberOf module:zrender/core/util
   * @param {Object|Array} obj
   * @param {Function} cb
   * @param {*} [context]
   */
  function each(obj, cb, context) {
      if (!(obj && cb)) {
          return;
      }
      if (obj.forEach && obj.forEach === nativeForEach) {
          obj.forEach(cb, context);
      }
      else if (obj.length === +obj.length) {
          for (var i = 0, len = obj.length; i < len; i++) {
              cb.call(context, obj[i], i, obj);
          }
      }
      else {
          for (var key in obj) {
              if (obj.hasOwnProperty(key)) {
                  cb.call(context, obj[key], key, obj);
              }
          }
      }
  }

  /**
   * 数组映射
   * @memberOf module:zrender/core/util
   * @param {Array} obj
   * @param {Function} cb
   * @param {*} [context]
   * @return {Array}
   */
  function map(obj, cb, context) {
      if (!(obj && cb)) {
          return;
      }
      if (obj.map && obj.map === nativeMap) {
          return obj.map(cb, context);
      }
      else {
          var result = [];
          for (var i = 0, len = obj.length; i < len; i++) {
              result.push(cb.call(context, obj[i], i, obj));
          }
          return result;
      }
  }

  /**
   * @memberOf module:zrender/core/util
   * @param {Function} func
   * @param {*} context
   * @return {Function}
   */
  function bind(func, context) {
      var args = nativeSlice.call(arguments, 2);
      return function () {
          return func.apply(context, args.concat(nativeSlice.call(arguments)));
      };
  }

  /**
   * @memberOf module:zrender/core/util
   * @param {*} value
   * @return {boolean}
   */
  function isArray(value) {
      return objToString.call(value) === '[object Array]';
  }

  /**
   * @memberOf module:zrender/core/util
   * @param {*} value
   * @return {boolean}
   */
  function isFunction(value) {
      return typeof value === 'function';
  }

  /**
   * @memberOf module:zrender/core/util
   * @param {*} value
   * @return {boolean}
   */
  function isString(value) {
      return objToString.call(value) === '[object String]';
  }

  /**
   * @memberOf module:zrender/core/util
   * @param {*} value
   * @return {boolean}
   */
  function isObject(value) {
      // Avoid a V8 JIT bug in Chrome 19-20.
      // See https://code.google.com/p/v8/issues/detail?id=2291 for more details.
      var type = typeof value;
      return type === 'function' || (!!value && type === 'object');
  }

  /**
   * @memberOf module:zrender/core/util
   * @param {*} value
   * @return {boolean}
   */
  function isBuiltInObject(value) {
      return !!BUILTIN_OBJECT[objToString.call(value)];
  }

  /**
   * @memberOf module:zrender/core/util
   * @param {*} value
   * @return {boolean}
   */
  function isDom(value) {
      return typeof value === 'object'
          && typeof value.nodeType === 'number'
          && typeof value.ownerDocument === 'object';
  }

  function retrieve2(value0, value1) {
      return value0 != null
          ? value0
          : value1;
  }

  function retrieve3(value0, value1, value2) {
      return value0 != null
          ? value0
          : value1 != null
          ? value1
          : value2;
  }

  /**
   * Normalize css liked array configuration
   * e.g.
   *  3 => [3, 3, 3, 3]
   *  [4, 2] => [4, 2, 4, 2]
   *  [4, 3, 2] => [4, 3, 2, 3]
   * @param {number|Array.<number>} val
   * @return {Array.<number>}
   */
  function normalizeCssArray(val) {
      if (typeof (val) === 'number') {
          return [val, val, val, val];
      }
      var len = val.length;
      if (len === 2) {
          // vertical | horizontal
          return [val[0], val[1], val[0], val[1]];
      }
      else if (len === 3) {
          // top | horizontal | bottom
          return [val[0], val[1], val[2], val[1]];
      }
      return val;
  }

  /**
   * @memberOf module:zrender/core/util
   * @param {string} str string to be trimed
   * @return {string} trimed string
   */
  function trim(str) {
      if (str == null) {
          return null;
      }
      else if (typeof str.trim === 'function') {
          return str.trim();
      }
      else {
          return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
      }
  }

  var primitiveKey = '__ec_primitive__';

  function isPrimitive(obj) {
      return obj[primitiveKey];
  }

  var SHADOW_PROPS = {
      'shadowBlur': 1,
      'shadowOffsetX': 1,
      'shadowOffsetY': 1,
      'textShadowBlur': 1,
      'textShadowOffsetX': 1,
      'textShadowOffsetY': 1,
      'textBoxShadowBlur': 1,
      'textBoxShadowOffsetX': 1,
      'textBoxShadowOffsetY': 1
  };

  function fixShadow (ctx, propName, value) {
      if (SHADOW_PROPS.hasOwnProperty(propName)) {
          return value *= ctx.dpr;
      }
      return value;
  }

  var ContextCachedBy = {
      NONE: 0,
      STYLE_BIND: 1,
      PLAIN_TEXT: 2
  };

  // Avoid confused with 0/false.
  var WILL_BE_RESTORED = 9;

  var STYLE_COMMON_PROPS = [
      ['shadowBlur', 0], ['shadowOffsetX', 0], ['shadowOffsetY', 0], ['shadowColor', '#000'],
      ['lineCap', 'butt'], ['lineJoin', 'miter'], ['miterLimit', 10]
  ];

  // var SHADOW_PROPS = STYLE_COMMON_PROPS.slice(0, 4);
  // var LINE_PROPS = STYLE_COMMON_PROPS.slice(4);

  var Style = function (opts) {
      this.extendFrom(opts, false);
  };

  function createLinearGradient(ctx, obj, rect) {
      var x = obj.x == null ? 0 : obj.x;
      var x2 = obj.x2 == null ? 1 : obj.x2;
      var y = obj.y == null ? 0 : obj.y;
      var y2 = obj.y2 == null ? 0 : obj.y2;

      if (!obj.global) {
          x = x * rect.width + rect.x;
          x2 = x2 * rect.width + rect.x;
          y = y * rect.height + rect.y;
          y2 = y2 * rect.height + rect.y;
      }

      // Fix NaN when rect is Infinity
      x = isNaN(x) ? 0 : x;
      x2 = isNaN(x2) ? 1 : x2;
      y = isNaN(y) ? 0 : y;
      y2 = isNaN(y2) ? 0 : y2;

      var canvasGradient = ctx.createLinearGradient(x, y, x2, y2);

      return canvasGradient;
  }

  function createRadialGradient(ctx, obj, rect) {
      var width = rect.width;
      var height = rect.height;
      var min = Math.min(width, height);

      var x = obj.x == null ? 0.5 : obj.x;
      var y = obj.y == null ? 0.5 : obj.y;
      var r = obj.r == null ? 0.5 : obj.r;
      if (!obj.global) {
          x = x * width + rect.x;
          y = y * height + rect.y;
          r = r * min;
      }

      var canvasGradient = ctx.createRadialGradient(x, y, 0, x, y, r);

      return canvasGradient;
  }


  Style.prototype = {

      constructor: Style,

      /**
       * @type {string}
       */
      fill: '#000',

      /**
       * @type {string}
       */
      stroke: null,

      /**
       * @type {number}
       */
      opacity: 1,

      /**
       * @type {number}
       */
      fillOpacity: null,

      /**
       * @type {number}
       */
      strokeOpacity: null,

      /**
       * @type {Array.<number>}
       */
      lineDash: null,

      /**
       * @type {number}
       */
      lineDashOffset: 0,

      /**
       * @type {number}
       */
      shadowBlur: 0,

      /**
       * @type {number}
       */
      shadowOffsetX: 0,

      /**
       * @type {number}
       */
      shadowOffsetY: 0,

      /**
       * @type {number}
       */
      lineWidth: 1,

      /**
       * If stroke ignore scale
       * @type {Boolean}
       */
      strokeNoScale: false,

      // Bounding rect text configuration
      // Not affected by element transform
      /**
       * @type {string}
       */
      text: null,

      /**
       * If `fontSize` or `fontFamily` exists, `font` will be reset by
       * `fontSize`, `fontStyle`, `fontWeight`, `fontFamily`.
       * So do not visit it directly in upper application (like echarts),
       * but use `contain/text#makeFont` instead.
       * @type {string}
       */
      font: null,

      /**
       * The same as font. Use font please.
       * @deprecated
       * @type {string}
       */
      textFont: null,

      /**
       * It helps merging respectively, rather than parsing an entire font string.
       * @type {string}
       */
      fontStyle: null,

      /**
       * It helps merging respectively, rather than parsing an entire font string.
       * @type {string}
       */
      fontWeight: null,

      /**
       * It helps merging respectively, rather than parsing an entire font string.
       * Should be 12 but not '12px'.
       * @type {number}
       */
      fontSize: null,

      /**
       * It helps merging respectively, rather than parsing an entire font string.
       * @type {string}
       */
      fontFamily: null,

      /**
       * Reserved for special functinality, like 'hr'.
       * @type {string}
       */
      textTag: null,

      /**
       * @type {string}
       */
      textFill: '#000',

      /**
       * @type {string}
       */
      textStroke: null,

      /**
       * @type {number}
       */
      textWidth: null,

      /**
       * Only for textBackground.
       * @type {number}
       */
      textHeight: null,

      /**
       * textStroke may be set as some color as a default
       * value in upper applicaion, where the default value
       * of textStrokeWidth should be 0 to make sure that
       * user can choose to do not use text stroke.
       * @type {number}
       */
      textStrokeWidth: 0,

      /**
       * @type {number}
       */
      textLineHeight: null,

      /**
       * 'inside', 'left', 'right', 'top', 'bottom'
       * [x, y]
       * Based on x, y of rect.
       * @type {string|Array.<number>}
       * @default 'inside'
       */
      textPosition: 'inside',

      /**
       * If not specified, use the boundingRect of a `displayable`.
       * @type {Object}
       */
      textRect: null,

      /**
       * [x, y]
       * @type {Array.<number>}
       */
      textOffset: null,

      /**
       * @type {string}
       */
      textAlign: null,

      /**
       * @type {string}
       */
      textVerticalAlign: null,

      /**
       * @type {number}
       */
      textDistance: 5,

      /**
       * @type {string}
       */
      textShadowColor: 'transparent',

      /**
       * @type {number}
       */
      textShadowBlur: 0,

      /**
       * @type {number}
       */
      textShadowOffsetX: 0,

      /**
       * @type {number}
       */
      textShadowOffsetY: 0,

      /**
       * @type {string}
       */
      textBoxShadowColor: 'transparent',

      /**
       * @type {number}
       */
      textBoxShadowBlur: 0,

      /**
       * @type {number}
       */
      textBoxShadowOffsetX: 0,

      /**
       * @type {number}
       */
      textBoxShadowOffsetY: 0,

      /**
       * Whether transform text.
       * Only useful in Path and Image element
       * @type {boolean}
       */
      transformText: false,

      /**
       * Text rotate around position of Path or Image
       * Only useful in Path and Image element and transformText is false.
       */
      textRotation: 0,

      /**
       * Text origin of text rotation, like [10, 40].
       * Based on x, y of rect.
       * Useful in label rotation of circular symbol.
       * By default, this origin is textPosition.
       * Can be 'center'.
       * @type {string|Array.<number>}
       */
      textOrigin: null,

      /**
       * @type {string}
       */
      textBackgroundColor: null,

      /**
       * @type {string}
       */
      textBorderColor: null,

      /**
       * @type {number}
       */
      textBorderWidth: 0,

      /**
       * @type {number}
       */
      textBorderRadius: 0,

      /**
       * Can be `2` or `[2, 4]` or `[2, 3, 4, 5]`
       * @type {number|Array.<number>}
       */
      textPadding: null,

      /**
       * Text styles for rich text.
       * @type {Object}
       */
      rich: null,

      /**
       * {outerWidth, outerHeight, ellipsis, placeholder}
       * @type {Object}
       */
      truncate: null,

      /**
       * https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation
       * @type {string}
       */
      blend: null,

      /**
       * @param {CanvasRenderingContext2D} ctx
       */
      bind: function (ctx, el, prevEl) {
          var style = this;
          var prevStyle = prevEl && prevEl.style;
          // If no prevStyle, it means first draw.
          // Only apply cache if the last time cachced by this function.
          var notCheckCache = !prevStyle || ctx.__attrCachedBy !== ContextCachedBy.STYLE_BIND;

          ctx.__attrCachedBy = ContextCachedBy.STYLE_BIND;

          for (var i = 0; i < STYLE_COMMON_PROPS.length; i++) {
              var prop = STYLE_COMMON_PROPS[i];
              var styleName = prop[0];

              if (notCheckCache || style[styleName] !== prevStyle[styleName]) {
                  // FIXME Invalid property value will cause style leak from previous element.
                  ctx[styleName] =
                      fixShadow(ctx, styleName, style[styleName] || prop[1]);
              }
          }

          if ((notCheckCache || style.fill !== prevStyle.fill)) {
              ctx.fillStyle = style.fill;
          }
          if ((notCheckCache || style.stroke !== prevStyle.stroke)) {
              ctx.strokeStyle = style.stroke;
          }
          if ((notCheckCache || style.opacity !== prevStyle.opacity)) {
              ctx.globalAlpha = style.opacity == null ? 1 : style.opacity;
          }

          if ((notCheckCache || style.blend !== prevStyle.blend)) {
              ctx.globalCompositeOperation = style.blend || 'source-over';
          }
          if (this.hasStroke()) {
              var lineWidth = style.lineWidth;
              ctx.lineWidth = lineWidth / (
                  (this.strokeNoScale && el && el.getLineScale) ? el.getLineScale() : 1
              );
          }
      },

      hasFill: function () {
          var fill = this.fill;
          return fill != null && fill !== 'none';
      },

      hasStroke: function () {
          var stroke = this.stroke;
          return stroke != null && stroke !== 'none' && this.lineWidth > 0;
      },

      /**
       * Extend from other style
       * @param {zrender/graphic/Style} otherStyle
       * @param {boolean} overwrite true: overwrirte any way.
       *                            false: overwrite only when !target.hasOwnProperty
       *                            others: overwrite when property is not null/undefined.
       */
      extendFrom: function (otherStyle, overwrite) {
          if (otherStyle) {
              for (var name in otherStyle) {
                  if (otherStyle.hasOwnProperty(name)
                      && (overwrite === true
                          || (
                              overwrite === false
                                  ? !this.hasOwnProperty(name)
                                  : otherStyle[name] != null
                          )
                      )
                  ) {
                      this[name] = otherStyle[name];
                  }
              }
          }
      },

      /**
       * Batch setting style with a given object
       * @param {Object|string} obj
       * @param {*} [obj]
       */
      set: function (obj, value) {
          if (typeof obj === 'string') {
              this[obj] = value;
          }
          else {
              this.extendFrom(obj, true);
          }
      },

      /**
       * Clone
       * @return {zrender/graphic/Style} [description]
       */
      clone: function () {
          var newStyle = new this.constructor();
          newStyle.extendFrom(this, true);
          return newStyle;
      },

      getGradient: function (ctx, obj, rect) {
          var method = obj.type === 'radial' ? createRadialGradient : createLinearGradient;
          var canvasGradient = method(ctx, obj, rect);
          var colorStops = obj.colorStops;
          for (var i = 0; i < colorStops.length; i++) {
              canvasGradient.addColorStop(
                  colorStops[i].offset, colorStops[i].color
              );
          }
          return canvasGradient;
      }

  };

  var styleProto = Style.prototype;
  for (var i = 0; i < STYLE_COMMON_PROPS.length; i++) {
      var prop = STYLE_COMMON_PROPS[i];
      if (!(prop[0] in styleProto)) {
          styleProto[prop[0]] = prop[1];
      }
  }

  // Provide for others
  Style.getGradient = styleProto.getGradient;

  /**
   * Event Mixin
   * @module zrender/mixin/Eventful
   * @author Kener (@Kener-林峰, kener.linfeng@gmail.com)
   *         pissang (https://www.github.com/pissang)
   */

  var arrySlice = Array.prototype.slice;

  /**
   * Event dispatcher.
   *
   * @alias module:zrender/mixin/Eventful
   * @constructor
   * @param {Object} [eventProcessor] The object eventProcessor is the scope when
   *        `eventProcessor.xxx` called.
   * @param {Function} [eventProcessor.normalizeQuery]
   *        param: {string|Object} Raw query.
   *        return: {string|Object} Normalized query.
   * @param {Function} [eventProcessor.filter] Event will be dispatched only
   *        if it returns `true`.
   *        param: {string} eventType
   *        param: {string|Object} query
   *        return: {boolean}
   * @param {Function} [eventProcessor.afterTrigger] Call after all handlers called.
   *        param: {string} eventType
   */
  var Eventful = function (eventProcessor) {
      this._$handlers = {};
      this._$eventProcessor = eventProcessor;
  };

  Eventful.prototype = {

      constructor: Eventful,

      /**
       * The handler can only be triggered once, then removed.
       *
       * @param {string} event The event name.
       * @param {string|Object} [query] Condition used on event filter.
       * @param {Function} handler The event handler.
       * @param {Object} context
       */
      one: function (event, query, handler, context) {
          return on(this, event, query, handler, context, true);
      },

      /**
       * Bind a handler.
       *
       * @param {string} event The event name.
       * @param {string|Object} [query] Condition used on event filter.
       * @param {Function} handler The event handler.
       * @param {Object} [context]
       */
      on: function (event, query, handler, context) {
          return on(this, event, query, handler, context, false);
      },

      /**
       * Whether any handler has bound.
       *
       * @param  {string}  event
       * @return {boolean}
       */
      isSilent: function (event) {
          var _h = this._$handlers;
          return !_h[event] || !_h[event].length;
      },

      /**
       * Unbind a event.
       *
       * @param {string} event The event name.
       * @param {Function} [handler] The event handler.
       */
      off: function (event, handler) {
          var _h = this._$handlers;

          if (!event) {
              this._$handlers = {};
              return this;
          }

          if (handler) {
              if (_h[event]) {
                  var newList = [];
                  for (var i = 0, l = _h[event].length; i < l; i++) {
                      if (_h[event][i].h !== handler) {
                          newList.push(_h[event][i]);
                      }
                  }
                  _h[event] = newList;
              }

              if (_h[event] && _h[event].length === 0) {
                  delete _h[event];
              }
          }
          else {
              delete _h[event];
          }

          return this;
      },

      /**
       * Dispatch a event.
       *
       * @param {string} type The event name.
       */
      trigger: function (type) {
          var _h = this._$handlers[type];
          var eventProcessor = this._$eventProcessor;

          if (_h) {
              var args = arguments;
              var argLen = args.length;

              if (argLen > 3) {
                  args = arrySlice.call(args, 1);
              }

              var len = _h.length;
              for (var i = 0; i < len;) {
                  var hItem = _h[i];
                  if (eventProcessor
                      && eventProcessor.filter
                      && hItem.query != null
                      && !eventProcessor.filter(type, hItem.query)
                  ) {
                      i++;
                      continue;
                  }

                  // Optimize advise from backbone
                  switch (argLen) {
                      case 1:
                          hItem.h.call(hItem.ctx);
                          break;
                      case 2:
                          hItem.h.call(hItem.ctx, args[1]);
                          break;
                      case 3:
                          hItem.h.call(hItem.ctx, args[1], args[2]);
                          break;
                      default:
                          // have more than 2 given arguments
                          hItem.h.apply(hItem.ctx, args);
                          break;
                  }

                  if (hItem.one) {
                      _h.splice(i, 1);
                      len--;
                  }
                  else {
                      i++;
                  }
              }
          }

          eventProcessor && eventProcessor.afterTrigger
              && eventProcessor.afterTrigger(type);

          return this;
      },

      /**
       * Dispatch a event with context, which is specified at the last parameter.
       *
       * @param {string} type The event name.
       */
      triggerWithContext: function (type) {
          var _h = this._$handlers[type];
          var eventProcessor = this._$eventProcessor;

          if (_h) {
              var args = arguments;
              var argLen = args.length;

              if (argLen > 4) {
                  args = arrySlice.call(args, 1, args.length - 1);
              }
              var ctx = args[args.length - 1];

              var len = _h.length;
              for (var i = 0; i < len;) {
                  var hItem = _h[i];
                  if (eventProcessor
                      && eventProcessor.filter
                      && hItem.query != null
                      && !eventProcessor.filter(type, hItem.query)
                  ) {
                      i++;
                      continue;
                  }

                  // Optimize advise from backbone
                  switch (argLen) {
                      case 1:
                          hItem.h.call(ctx);
                          break;
                      case 2:
                          hItem.h.call(ctx, args[1]);
                          break;
                      case 3:
                          hItem.h.call(ctx, args[1], args[2]);
                          break;
                      default:
                          // have more than 2 given arguments
                          hItem.h.apply(ctx, args);
                          break;
                  }

                  if (hItem.one) {
                      _h.splice(i, 1);
                      len--;
                  }
                  else {
                      i++;
                  }
              }
          }

          eventProcessor && eventProcessor.afterTrigger
              && eventProcessor.afterTrigger(type);

          return this;
      }
  };

  function normalizeQuery(host, query) {
      var eventProcessor = host._$eventProcessor;
      if (query != null && eventProcessor && eventProcessor.normalizeQuery) {
          query = eventProcessor.normalizeQuery(query);
      }
      return query;
  }

  function on(eventful, event, query, handler, context, isOnce) {
      var _h = eventful._$handlers;

      if (typeof query === 'function') {
          context = handler;
          handler = query;
          query = null;
      }

      if (!handler || !event) {
          return eventful;
      }

      query = normalizeQuery(eventful, query);

      if (!_h[event]) {
          _h[event] = [];
      }

      for (var i = 0; i < _h[event].length; i++) {
          if (_h[event][i].h === handler) {
              return eventful;
          }
      }

      var wrap = {
          h: handler,
          one: isOnce,
          query: query,
          ctx: context || eventful,
          // FIXME
          // Do not publish this feature util it is proved that it makes sense.
          callAtLast: handler.zrEventfulCallAtLast
      };

      var lastIndex = _h[event].length - 1;
      var lastWrap = _h[event][lastIndex];
      (lastWrap && lastWrap.callAtLast)
          ? _h[event].splice(lastIndex, 0, wrap)
          : _h[event].push(wrap);

      return eventful;
  }

  /**
   * 3x2矩阵操作类
   * @exports zrender/tool/matrix
   */

  var ArrayCtor = typeof Float32Array === 'undefined'
      ? Array
      : Float32Array;

  /**
   * Create a identity matrix.
   * @return {Float32Array|Array.<number>}
   */
  function create() {
      var out = new ArrayCtor(6);
      identity(out);

      return out;
  }

  /**
   * 设置矩阵为单位矩阵
   * @param {Float32Array|Array.<number>} out
   */
  function identity(out) {
      out[0] = 1;
      out[1] = 0;
      out[2] = 0;
      out[3] = 1;
      out[4] = 0;
      out[5] = 0;
      return out;
  }

  /**
   * 复制矩阵
   * @param {Float32Array|Array.<number>} out
   * @param {Float32Array|Array.<number>} m
   */
  function copy(out, m) {
      out[0] = m[0];
      out[1] = m[1];
      out[2] = m[2];
      out[3] = m[3];
      out[4] = m[4];
      out[5] = m[5];
      return out;
  }

  /**
   * 矩阵相乘
   * @param {Float32Array|Array.<number>} out
   * @param {Float32Array|Array.<number>} m1
   * @param {Float32Array|Array.<number>} m2
   */
  function mul(out, m1, m2) {
      // Consider matrix.mul(m, m2, m);
      // where out is the same as m2.
      // So use temp variable to escape error.
      var out0 = m1[0] * m2[0] + m1[2] * m2[1];
      var out1 = m1[1] * m2[0] + m1[3] * m2[1];
      var out2 = m1[0] * m2[2] + m1[2] * m2[3];
      var out3 = m1[1] * m2[2] + m1[3] * m2[3];
      var out4 = m1[0] * m2[4] + m1[2] * m2[5] + m1[4];
      var out5 = m1[1] * m2[4] + m1[3] * m2[5] + m1[5];
      out[0] = out0;
      out[1] = out1;
      out[2] = out2;
      out[3] = out3;
      out[4] = out4;
      out[5] = out5;
      return out;
  }

  /**
   * 平移变换
   * @param {Float32Array|Array.<number>} out
   * @param {Float32Array|Array.<number>} a
   * @param {Float32Array|Array.<number>} v
   */
  function translate(out, a, v) {
      out[0] = a[0];
      out[1] = a[1];
      out[2] = a[2];
      out[3] = a[3];
      out[4] = a[4] + v[0];
      out[5] = a[5] + v[1];
      return out;
  }

  /**
   * 旋转变换
   * @param {Float32Array|Array.<number>} out
   * @param {Float32Array|Array.<number>} a
   * @param {number} rad
   */
  function rotate(out, a, rad) {
      var aa = a[0];
      var ac = a[2];
      var atx = a[4];
      var ab = a[1];
      var ad = a[3];
      var aty = a[5];
      var st = Math.sin(rad);
      var ct = Math.cos(rad);

      out[0] = aa * ct + ab * st;
      out[1] = -aa * st + ab * ct;
      out[2] = ac * ct + ad * st;
      out[3] = -ac * st + ct * ad;
      out[4] = ct * atx + st * aty;
      out[5] = ct * aty - st * atx;
      return out;
  }

  /**
   * 缩放变换
   * @param {Float32Array|Array.<number>} out
   * @param {Float32Array|Array.<number>} a
   * @param {Float32Array|Array.<number>} v
   */
  function scale(out, a, v) {
      var vx = v[0];
      var vy = v[1];
      out[0] = a[0] * vx;
      out[1] = a[1] * vy;
      out[2] = a[2] * vx;
      out[3] = a[3] * vy;
      out[4] = a[4] * vx;
      out[5] = a[5] * vy;
      return out;
  }

  /**
   * 求逆矩阵
   * @param {Float32Array|Array.<number>} out
   * @param {Float32Array|Array.<number>} a
   */
  function invert(out, a) {

      var aa = a[0];
      var ac = a[2];
      var atx = a[4];
      var ab = a[1];
      var ad = a[3];
      var aty = a[5];

      var det = aa * ad - ab * ac;
      if (!det) {
          return null;
      }
      det = 1.0 / det;

      out[0] = ad * det;
      out[1] = -ab * det;
      out[2] = -ac * det;
      out[3] = aa * det;
      out[4] = (ac * aty - ad * atx) * det;
      out[5] = (ab * atx - aa * aty) * det;
      return out;
  }

  var ArrayCtor$1 = typeof Float32Array === 'undefined'
      ? Array
      : Float32Array;

  /**
   * 创建一个向量
   * @param {number} [x=0]
   * @param {number} [y=0]
   * @return {Vector2}
   */
  function create$1(x, y) {
      var out = new ArrayCtor$1(2);
      if (x == null) {
          x = 0;
      }
      if (y == null) {
          y = 0;
      }
      out[0] = x;
      out[1] = y;
      return out;
  }

  /**
   * 向量长度
   * @param {Vector2} v
   * @return {number}
   */
  function len(v) {
      return Math.sqrt(lenSquare(v));
  }

  /**
   * 向量长度平方
   * @param {Vector2} v
   * @return {number}
   */
  function lenSquare(v) {
      return v[0] * v[0] + v[1] * v[1];
  }

  /**
   * 向量归一化
   * @param {Vector2} out
   * @param {Vector2} v
   */
  function normalize(out, v) {
      var d = len(v);
      if (d === 0) {
          out[0] = 0;
          out[1] = 0;
      }
      else {
          out[0] = v[0] / d;
          out[1] = v[1] / d;
      }
      return out;
  }

  /**
   * 计算向量间距离
   * @param {Vector2} v1
   * @param {Vector2} v2
   * @return {number}
   */
  function distance(v1, v2) {
      return Math.sqrt(
          (v1[0] - v2[0]) * (v1[0] - v2[0])
          + (v1[1] - v2[1]) * (v1[1] - v2[1])
      );
  }
  var dist = distance;

  /**
   * 向量距离平方
   * @param {Vector2} v1
   * @param {Vector2} v2
   * @return {number}
   */
  function distanceSquare(v1, v2) {
      return (v1[0] - v2[0]) * (v1[0] - v2[0])
          + (v1[1] - v2[1]) * (v1[1] - v2[1]);
  }
  var distSquare = distanceSquare;

  /**
   * 矩阵左乘向量
   * @param {Vector2} out
   * @param {Vector2} v
   * @param {Vector2} m
   */
  function applyTransform(out, v, m) {
      var x = v[0];
      var y = v[1];
      out[0] = m[0] * x + m[2] * y + m[4];
      out[1] = m[1] * x + m[3] * y + m[5];
      return out;
  }

  /**
   * 求两个向量最小值
   * @param  {Vector2} out
   * @param  {Vector2} v1
   * @param  {Vector2} v2
   */
  function min$1(out, v1, v2) {
      out[0] = Math.min(v1[0], v2[0]);
      out[1] = Math.min(v1[1], v2[1]);
      return out;
  }

  /**
   * 求两个向量最大值
   * @param  {Vector2} out
   * @param  {Vector2} v1
   * @param  {Vector2} v2
   */
  function max(out, v1, v2) {
      out[0] = Math.max(v1[0], v2[0]);
      out[1] = Math.max(v1[1], v2[1]);
      return out;
  }

  /**
   * 提供变换扩展
   * @module zrender/mixin/Transformable
   * @author pissang (https://www.github.com/pissang)
   */

  var mIdentity = identity;

  var EPSILON = 5e-5;

  function isNotAroundZero(val) {
      return val > EPSILON || val < -EPSILON;
  }

  /**
   * @alias module:zrender/mixin/Transformable
   * @constructor
   */
  var Transformable = function (opts) {
      opts = opts || {};
      // If there are no given position, rotation, scale
      if (!opts.position) {
          /**
           * 平移
           * @type {Array.<number>}
           * @default [0, 0]
           */
          this.position = [0, 0];
      }
      if (opts.rotation == null) {
          /**
           * 旋转
           * @type {Array.<number>}
           * @default 0
           */
          this.rotation = 0;
      }
      if (!opts.scale) {
          /**
           * 缩放
           * @type {Array.<number>}
           * @default [1, 1]
           */
          this.scale = [1, 1];
      }
      /**
       * 旋转和缩放的原点
       * @type {Array.<number>}
       * @default null
       */
      this.origin = this.origin || null;
  };

  var transformableProto = Transformable.prototype;
  transformableProto.transform = null;

  /**
   * 判断是否需要有坐标变换
   * 如果有坐标变换, 则从position, rotation, scale以及父节点的transform计算出自身的transform矩阵
   */
  transformableProto.needLocalTransform = function () {
      return isNotAroundZero(this.rotation)
          || isNotAroundZero(this.position[0])
          || isNotAroundZero(this.position[1])
          || isNotAroundZero(this.scale[0] - 1)
          || isNotAroundZero(this.scale[1] - 1);
  };

  var scaleTmp = [];
  transformableProto.updateTransform = function () {
      var parent = this.parent;
      var parentHasTransform = parent && parent.transform;
      var needLocalTransform = this.needLocalTransform();

      var m = this.transform;
      if (!(needLocalTransform || parentHasTransform)) {
          m && mIdentity(m);
          return;
      }

      m = m || create();

      if (needLocalTransform) {
          this.getLocalTransform(m);
      }
      else {
          mIdentity(m);
      }

      // 应用父节点变换
      if (parentHasTransform) {
          if (needLocalTransform) {
              mul(m, parent.transform, m);
          }
          else {
              copy(m, parent.transform);
          }
      }
      // 保存这个变换矩阵
      this.transform = m;

      var globalScaleRatio = this.globalScaleRatio;
      if (globalScaleRatio != null && globalScaleRatio !== 1) {
          this.getGlobalScale(scaleTmp);
          var relX = scaleTmp[0] < 0 ? -1 : 1;
          var relY = scaleTmp[1] < 0 ? -1 : 1;
          var sx = ((scaleTmp[0] - relX) * globalScaleRatio + relX) / scaleTmp[0] || 0;
          var sy = ((scaleTmp[1] - relY) * globalScaleRatio + relY) / scaleTmp[1] || 0;

          m[0] *= sx;
          m[1] *= sx;
          m[2] *= sy;
          m[3] *= sy;
      }

      this.invTransform = this.invTransform || create();
      invert(this.invTransform, m);
  };

  transformableProto.getLocalTransform = function (m) {
      return Transformable.getLocalTransform(this, m);
  };

  /**
   * 将自己的transform应用到context上
   * @param {CanvasRenderingContext2D} ctx
   */
  transformableProto.setTransform = function (ctx) {
      var m = this.transform;
      var dpr = ctx.dpr || 1;
      if (m) {
          ctx.setTransform(dpr * m[0], dpr * m[1], dpr * m[2], dpr * m[3], dpr * m[4], dpr * m[5]);
      }
      else {
          ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
      }
  };

  transformableProto.restoreTransform = function (ctx) {
      var dpr = ctx.dpr || 1;
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
  };

  var tmpTransform = [];
  var originTransform = create();

  transformableProto.setLocalTransform = function (m) {
      if (!m) {
          // TODO return or set identity?
          return;
      }
      var sx = m[0] * m[0] + m[1] * m[1];
      var sy = m[2] * m[2] + m[3] * m[3];
      var position = this.position;
      var scale = this.scale;
      if (isNotAroundZero(sx - 1)) {
          sx = Math.sqrt(sx);
      }
      if (isNotAroundZero(sy - 1)) {
          sy = Math.sqrt(sy);
      }
      if (m[0] < 0) {
          sx = -sx;
      }
      if (m[3] < 0) {
          sy = -sy;
      }

      position[0] = m[4];
      position[1] = m[5];
      scale[0] = sx;
      scale[1] = sy;
      this.rotation = Math.atan2(-m[1] / sy, m[0] / sx);
  };
  /**
   * 分解`transform`矩阵到`position`, `rotation`, `scale`
   */
  transformableProto.decomposeTransform = function () {
      if (!this.transform) {
          return;
      }
      var parent = this.parent;
      var m = this.transform;
      if (parent && parent.transform) {
          // Get local transform and decompose them to position, scale, rotation
          mul(tmpTransform, parent.invTransform, m);
          m = tmpTransform;
      }
      var origin = this.origin;
      if (origin && (origin[0] || origin[1])) {
          originTransform[4] = origin[0];
          originTransform[5] = origin[1];
          mul(tmpTransform, m, originTransform);
          tmpTransform[4] -= origin[0];
          tmpTransform[5] -= origin[1];
          m = tmpTransform;
      }

      this.setLocalTransform(m);
  };

  /**
   * Get global scale
   * @return {Array.<number>}
   */
  transformableProto.getGlobalScale = function (out) {
      var m = this.transform;
      out = out || [];
      if (!m) {
          out[0] = 1;
          out[1] = 1;
          return out;
      }
      out[0] = Math.sqrt(m[0] * m[0] + m[1] * m[1]);
      out[1] = Math.sqrt(m[2] * m[2] + m[3] * m[3]);
      if (m[0] < 0) {
          out[0] = -out[0];
      }
      if (m[3] < 0) {
          out[1] = -out[1];
      }
      return out;
  };
  /**
   * 变换坐标位置到 shape 的局部坐标空间
   * @method
   * @param {number} x
   * @param {number} y
   * @return {Array.<number>}
   */
  transformableProto.transformCoordToLocal = function (x, y) {
      var v2 = [x, y];
      var invTransform = this.invTransform;
      if (invTransform) {
          applyTransform(v2, v2, invTransform);
      }
      return v2;
  };

  /**
   * 变换局部坐标位置到全局坐标空间
   * @method
   * @param {number} x
   * @param {number} y
   * @return {Array.<number>}
   */
  transformableProto.transformCoordToGlobal = function (x, y) {
      var v2 = [x, y];
      var transform = this.transform;
      if (transform) {
          applyTransform(v2, v2, transform);
      }
      return v2;
  };

  /**
   * @static
   * @param {Object} target
   * @param {Array.<number>} target.origin
   * @param {number} target.rotation
   * @param {Array.<number>} target.position
   * @param {Array.<number>} [m]
   */
  Transformable.getLocalTransform = function (target, m) {
      m = m || [];
      mIdentity(m);

      var origin = target.origin;
      var scale$1 = target.scale || [1, 1];
      var rotation = target.rotation || 0;
      var position = target.position || [0, 0];

      if (origin) {
          // Translate to origin
          m[4] -= origin[0];
          m[5] -= origin[1];
      }
      scale(m, m, scale$1);
      if (rotation) {
          rotate(m, m, rotation);
      }
      if (origin) {
          // Translate back from origin
          m[4] += origin[0];
          m[5] += origin[1];
      }

      m[4] += position[0];
      m[5] += position[1];

      return m;
  };

  /**
   * 缓动代码来自 https://github.com/sole/tween.js/blob/master/src/Tween.js
   * @see http://sole.github.io/tween.js/examples/03_graphs.html
   * @exports zrender/animation/easing
   */
  var easing = {
      /**
      * @param {number} k
      * @return {number}
      */
      linear: function (k) {
          return k;
      },

      /**
      * @param {number} k
      * @return {number}
      */
      quadraticIn: function (k) {
          return k * k;
      },
      /**
      * @param {number} k
      * @return {number}
      */
      quadraticOut: function (k) {
          return k * (2 - k);
      },
      /**
      * @param {number} k
      * @return {number}
      */
      quadraticInOut: function (k) {
          if ((k *= 2) < 1) {
              return 0.5 * k * k;
          }
          return -0.5 * (--k * (k - 2) - 1);
      },

      // 三次方的缓动（t^3）
      /**
      * @param {number} k
      * @return {number}
      */
      cubicIn: function (k) {
          return k * k * k;
      },
      /**
      * @param {number} k
      * @return {number}
      */
      cubicOut: function (k) {
          return --k * k * k + 1;
      },
      /**
      * @param {number} k
      * @return {number}
      */
      cubicInOut: function (k) {
          if ((k *= 2) < 1) {
              return 0.5 * k * k * k;
          }
          return 0.5 * ((k -= 2) * k * k + 2);
      },

      // 四次方的缓动（t^4）
      /**
      * @param {number} k
      * @return {number}
      */
      quarticIn: function (k) {
          return k * k * k * k;
      },
      /**
      * @param {number} k
      * @return {number}
      */
      quarticOut: function (k) {
          return 1 - (--k * k * k * k);
      },
      /**
      * @param {number} k
      * @return {number}
      */
      quarticInOut: function (k) {
          if ((k *= 2) < 1) {
              return 0.5 * k * k * k * k;
          }
          return -0.5 * ((k -= 2) * k * k * k - 2);
      },

      // 五次方的缓动（t^5）
      /**
      * @param {number} k
      * @return {number}
      */
      quinticIn: function (k) {
          return k * k * k * k * k;
      },
      /**
      * @param {number} k
      * @return {number}
      */
      quinticOut: function (k) {
          return --k * k * k * k * k + 1;
      },
      /**
      * @param {number} k
      * @return {number}
      */
      quinticInOut: function (k) {
          if ((k *= 2) < 1) {
              return 0.5 * k * k * k * k * k;
          }
          return 0.5 * ((k -= 2) * k * k * k * k + 2);
      },

      // 正弦曲线的缓动（sin(t)）
      /**
      * @param {number} k
      * @return {number}
      */
      sinusoidalIn: function (k) {
          return 1 - Math.cos(k * Math.PI / 2);
      },
      /**
      * @param {number} k
      * @return {number}
      */
      sinusoidalOut: function (k) {
          return Math.sin(k * Math.PI / 2);
      },
      /**
      * @param {number} k
      * @return {number}
      */
      sinusoidalInOut: function (k) {
          return 0.5 * (1 - Math.cos(Math.PI * k));
      },

      // 指数曲线的缓动（2^t）
      /**
      * @param {number} k
      * @return {number}
      */
      exponentialIn: function (k) {
          return k === 0 ? 0 : Math.pow(1024, k - 1);
      },
      /**
      * @param {number} k
      * @return {number}
      */
      exponentialOut: function (k) {
          return k === 1 ? 1 : 1 - Math.pow(2, -10 * k);
      },
      /**
      * @param {number} k
      * @return {number}
      */
      exponentialInOut: function (k) {
          if (k === 0) {
              return 0;
          }
          if (k === 1) {
              return 1;
          }
          if ((k *= 2) < 1) {
              return 0.5 * Math.pow(1024, k - 1);
          }
          return 0.5 * (-Math.pow(2, -10 * (k - 1)) + 2);
      },

      // 圆形曲线的缓动（sqrt(1-t^2)）
      /**
      * @param {number} k
      * @return {number}
      */
      circularIn: function (k) {
          return 1 - Math.sqrt(1 - k * k);
      },
      /**
      * @param {number} k
      * @return {number}
      */
      circularOut: function (k) {
          return Math.sqrt(1 - (--k * k));
      },
      /**
      * @param {number} k
      * @return {number}
      */
      circularInOut: function (k) {
          if ((k *= 2) < 1) {
              return -0.5 * (Math.sqrt(1 - k * k) - 1);
          }
          return 0.5 * (Math.sqrt(1 - (k -= 2) * k) + 1);
      },

      // 创建类似于弹簧在停止前来回振荡的动画
      /**
      * @param {number} k
      * @return {number}
      */
      elasticIn: function (k) {
          var s;
          var a = 0.1;
          var p = 0.4;
          if (k === 0) {
              return 0;
          }
          if (k === 1) {
              return 1;
          }
          if (!a || a < 1) {
              a = 1;
              s = p / 4;
          }
          else {
              s = p * Math.asin(1 / a) / (2 * Math.PI);
          }
          return -(a * Math.pow(2, 10 * (k -= 1))
                      * Math.sin((k - s) * (2 * Math.PI) / p));
      },
      /**
      * @param {number} k
      * @return {number}
      */
      elasticOut: function (k) {
          var s;
          var a = 0.1;
          var p = 0.4;
          if (k === 0) {
              return 0;
          }
          if (k === 1) {
              return 1;
          }
          if (!a || a < 1) {
              a = 1;
              s = p / 4;
          }
          else {
              s = p * Math.asin(1 / a) / (2 * Math.PI);
          }
          return (a * Math.pow(2, -10 * k)
                      * Math.sin((k - s) * (2 * Math.PI) / p) + 1);
      },
      /**
      * @param {number} k
      * @return {number}
      */
      elasticInOut: function (k) {
          var s;
          var a = 0.1;
          var p = 0.4;
          if (k === 0) {
              return 0;
          }
          if (k === 1) {
              return 1;
          }
          if (!a || a < 1) {
              a = 1;
              s = p / 4;
          }
          else {
              s = p * Math.asin(1 / a) / (2 * Math.PI);
          }
          if ((k *= 2) < 1) {
              return -0.5 * (a * Math.pow(2, 10 * (k -= 1))
                  * Math.sin((k - s) * (2 * Math.PI) / p));
          }
          return a * Math.pow(2, -10 * (k -= 1))
                  * Math.sin((k - s) * (2 * Math.PI) / p) * 0.5 + 1;

      },

      // 在某一动画开始沿指示的路径进行动画处理前稍稍收回该动画的移动
      /**
      * @param {number} k
      * @return {number}
      */
      backIn: function (k) {
          var s = 1.70158;
          return k * k * ((s + 1) * k - s);
      },
      /**
      * @param {number} k
      * @return {number}
      */
      backOut: function (k) {
          var s = 1.70158;
          return --k * k * ((s + 1) * k + s) + 1;
      },
      /**
      * @param {number} k
      * @return {number}
      */
      backInOut: function (k) {
          var s = 1.70158 * 1.525;
          if ((k *= 2) < 1) {
              return 0.5 * (k * k * ((s + 1) * k - s));
          }
          return 0.5 * ((k -= 2) * k * ((s + 1) * k + s) + 2);
      },

      // 创建弹跳效果
      /**
      * @param {number} k
      * @return {number}
      */
      bounceIn: function (k) {
          return 1 - easing.bounceOut(1 - k);
      },
      /**
      * @param {number} k
      * @return {number}
      */
      bounceOut: function (k) {
          if (k < (1 / 2.75)) {
              return 7.5625 * k * k;
          }
          else if (k < (2 / 2.75)) {
              return 7.5625 * (k -= (1.5 / 2.75)) * k + 0.75;
          }
          else if (k < (2.5 / 2.75)) {
              return 7.5625 * (k -= (2.25 / 2.75)) * k + 0.9375;
          }
          else {
              return 7.5625 * (k -= (2.625 / 2.75)) * k + 0.984375;
          }
      },
      /**
      * @param {number} k
      * @return {number}
      */
      bounceInOut: function (k) {
          if (k < 0.5) {
              return easing.bounceIn(k * 2) * 0.5;
          }
          return easing.bounceOut(k * 2 - 1) * 0.5 + 0.5;
      }
  };

  /**
   * 动画主控制器
   * @config target 动画对象，可以是数组，如果是数组的话会批量分发onframe等事件
   * @config life(1000) 动画时长
   * @config delay(0) 动画延迟时间
   * @config loop(true)
   * @config gap(0) 循环的间隔时间
   * @config onframe
   * @config easing(optional)
   * @config ondestroy(optional)
   * @config onrestart(optional)
   *
   * TODO pause
   */

  function Clip(options) {

      this._target = options.target;

      // 生命周期
      this._life = options.life || 1000;
      // 延时
      this._delay = options.delay || 0;
      // 开始时间
      // this._startTime = new Date().getTime() + this._delay;// 单位毫秒
      this._initialized = false;

      // 是否循环
      this.loop = options.loop == null ? false : options.loop;

      this.gap = options.gap || 0;

      this.easing = options.easing || 'Linear';

      this.onframe = options.onframe;
      this.ondestroy = options.ondestroy;
      this.onrestart = options.onrestart;

      this._pausedTime = 0;
      this._paused = false;
  }

  Clip.prototype = {

      constructor: Clip,

      step: function (globalTime, deltaTime) {
          // Set startTime on first step, or _startTime may has milleseconds different between clips
          // PENDING
          if (!this._initialized) {
              this._startTime = globalTime + this._delay;
              this._initialized = true;
          }

          if (this._paused) {
              this._pausedTime += deltaTime;
              return;
          }

          var percent = (globalTime - this._startTime - this._pausedTime) / this._life;

          // 还没开始
          if (percent < 0) {
              return;
          }

          percent = Math.min(percent, 1);

          var easing$1 = this.easing;
          var easingFunc = typeof easing$1 === 'string' ? easing[easing$1] : easing$1;
          var schedule = typeof easingFunc === 'function'
              ? easingFunc(percent)
              : percent;

          this.fire('frame', schedule);

          // 结束
          if (percent === 1) {
              if (this.loop) {
                  this.restart(globalTime);
                  // 重新开始周期
                  // 抛出而不是直接调用事件直到 stage.update 后再统一调用这些事件
                  return 'restart';
              }

              // 动画完成将这个控制器标识为待删除
              // 在Animation.update中进行批量删除
              this._needsRemove = true;
              return 'destroy';
          }

          return null;
      },

      restart: function (globalTime) {
          var remainder = (globalTime - this._startTime - this._pausedTime) % this._life;
          this._startTime = globalTime - remainder + this.gap;
          this._pausedTime = 0;

          this._needsRemove = false;
      },

      fire: function (eventType, arg) {
          eventType = 'on' + eventType;
          if (this[eventType]) {
              this[eventType](this._target, arg);
          }
      },

      pause: function () {
          this._paused = true;
      },

      resume: function () {
          this._paused = false;
      }
  };

  // Simple LRU cache use doubly linked list
  // @module zrender/core/LRU

  /**
   * Simple double linked list. Compared with array, it has O(1) remove operation.
   * @constructor
   */
  var LinkedList = function () {

      /**
       * @type {module:zrender/core/LRU~Entry}
       */
      this.head = null;

      /**
       * @type {module:zrender/core/LRU~Entry}
       */
      this.tail = null;

      this._len = 0;
  };

  var linkedListProto = LinkedList.prototype;
  /**
   * Insert a new value at the tail
   * @param  {} val
   * @return {module:zrender/core/LRU~Entry}
   */
  linkedListProto.insert = function (val) {
      var entry = new Entry(val);
      this.insertEntry(entry);
      return entry;
  };

  /**
   * Insert an entry at the tail
   * @param  {module:zrender/core/LRU~Entry} entry
   */
  linkedListProto.insertEntry = function (entry) {
      if (!this.head) {
          this.head = this.tail = entry;
      }
      else {
          this.tail.next = entry;
          entry.prev = this.tail;
          entry.next = null;
          this.tail = entry;
      }
      this._len++;
  };

  /**
   * Remove entry.
   * @param  {module:zrender/core/LRU~Entry} entry
   */
  linkedListProto.remove = function (entry) {
      var prev = entry.prev;
      var next = entry.next;
      if (prev) {
          prev.next = next;
      }
      else {
          // Is head
          this.head = next;
      }
      if (next) {
          next.prev = prev;
      }
      else {
          // Is tail
          this.tail = prev;
      }
      entry.next = entry.prev = null;
      this._len--;
  };

  /**
   * @return {number}
   */
  linkedListProto.len = function () {
      return this._len;
  };

  /**
   * Clear list
   */
  linkedListProto.clear = function () {
      this.head = this.tail = null;
      this._len = 0;
  };

  /**
   * @constructor
   * @param {} val
   */
  var Entry = function (val) {
      /**
       * @type {}
       */
      this.value = val;

      /**
       * @type {module:zrender/core/LRU~Entry}
       */
      this.next;

      /**
       * @type {module:zrender/core/LRU~Entry}
       */
      this.prev;
  };

  /**
   * LRU Cache
   * @constructor
   * @alias module:zrender/core/LRU
   */
  var LRU = function (maxSize) {

      this._list = new LinkedList();

      this._map = {};

      this._maxSize = maxSize || 10;

      this._lastRemovedEntry = null;
  };

  var LRUProto = LRU.prototype;

  /**
   * @param  {string} key
   * @param  {} value
   * @return {} Removed value
   */
  LRUProto.put = function (key, value) {
      var list = this._list;
      var map = this._map;
      var removed = null;
      if (map[key] == null) {
          var len = list.len();
          // Reuse last removed entry
          var entry = this._lastRemovedEntry;

          if (len >= this._maxSize && len > 0) {
              // Remove the least recently used
              var leastUsedEntry = list.head;
              list.remove(leastUsedEntry);
              delete map[leastUsedEntry.key];

              removed = leastUsedEntry.value;
              this._lastRemovedEntry = leastUsedEntry;
          }

          if (entry) {
              entry.value = value;
          }
          else {
              entry = new Entry(value);
          }
          entry.key = key;
          list.insertEntry(entry);
          map[key] = entry;
      }

      return removed;
  };

  /**
   * @param  {string} key
   * @return {}
   */
  LRUProto.get = function (key) {
      var entry = this._map[key];
      var list = this._list;
      if (entry != null) {
          // Put the latest used entry in the tail
          if (entry !== list.tail) {
              list.remove(entry);
              list.insertEntry(entry);
          }

          return entry.value;
      }
  };

  /**
   * Clear the cache
   */
  LRUProto.clear = function () {
      this._list.clear();
      this._map = {};
  };

  var kCSSColorTable = {
      'transparent': [0, 0, 0, 0], 'aliceblue': [240, 248, 255, 1],
      'antiquewhite': [250, 235, 215, 1], 'aqua': [0, 255, 255, 1],
      'aquamarine': [127, 255, 212, 1], 'azure': [240, 255, 255, 1],
      'beige': [245, 245, 220, 1], 'bisque': [255, 228, 196, 1],
      'black': [0, 0, 0, 1], 'blanchedalmond': [255, 235, 205, 1],
      'blue': [0, 0, 255, 1], 'blueviolet': [138, 43, 226, 1],
      'brown': [165, 42, 42, 1], 'burlywood': [222, 184, 135, 1],
      'cadetblue': [95, 158, 160, 1], 'chartreuse': [127, 255, 0, 1],
      'chocolate': [210, 105, 30, 1], 'coral': [255, 127, 80, 1],
      'cornflowerblue': [100, 149, 237, 1], 'cornsilk': [255, 248, 220, 1],
      'crimson': [220, 20, 60, 1], 'cyan': [0, 255, 255, 1],
      'darkblue': [0, 0, 139, 1], 'darkcyan': [0, 139, 139, 1],
      'darkgoldenrod': [184, 134, 11, 1], 'darkgray': [169, 169, 169, 1],
      'darkgreen': [0, 100, 0, 1], 'darkgrey': [169, 169, 169, 1],
      'darkkhaki': [189, 183, 107, 1], 'darkmagenta': [139, 0, 139, 1],
      'darkolivegreen': [85, 107, 47, 1], 'darkorange': [255, 140, 0, 1],
      'darkorchid': [153, 50, 204, 1], 'darkred': [139, 0, 0, 1],
      'darksalmon': [233, 150, 122, 1], 'darkseagreen': [143, 188, 143, 1],
      'darkslateblue': [72, 61, 139, 1], 'darkslategray': [47, 79, 79, 1],
      'darkslategrey': [47, 79, 79, 1], 'darkturquoise': [0, 206, 209, 1],
      'darkviolet': [148, 0, 211, 1], 'deeppink': [255, 20, 147, 1],
      'deepskyblue': [0, 191, 255, 1], 'dimgray': [105, 105, 105, 1],
      'dimgrey': [105, 105, 105, 1], 'dodgerblue': [30, 144, 255, 1],
      'firebrick': [178, 34, 34, 1], 'floralwhite': [255, 250, 240, 1],
      'forestgreen': [34, 139, 34, 1], 'fuchsia': [255, 0, 255, 1],
      'gainsboro': [220, 220, 220, 1], 'ghostwhite': [248, 248, 255, 1],
      'gold': [255, 215, 0, 1], 'goldenrod': [218, 165, 32, 1],
      'gray': [128, 128, 128, 1], 'green': [0, 128, 0, 1],
      'greenyellow': [173, 255, 47, 1], 'grey': [128, 128, 128, 1],
      'honeydew': [240, 255, 240, 1], 'hotpink': [255, 105, 180, 1],
      'indianred': [205, 92, 92, 1], 'indigo': [75, 0, 130, 1],
      'ivory': [255, 255, 240, 1], 'khaki': [240, 230, 140, 1],
      'lavender': [230, 230, 250, 1], 'lavenderblush': [255, 240, 245, 1],
      'lawngreen': [124, 252, 0, 1], 'lemonchiffon': [255, 250, 205, 1],
      'lightblue': [173, 216, 230, 1], 'lightcoral': [240, 128, 128, 1],
      'lightcyan': [224, 255, 255, 1], 'lightgoldenrodyellow': [250, 250, 210, 1],
      'lightgray': [211, 211, 211, 1], 'lightgreen': [144, 238, 144, 1],
      'lightgrey': [211, 211, 211, 1], 'lightpink': [255, 182, 193, 1],
      'lightsalmon': [255, 160, 122, 1], 'lightseagreen': [32, 178, 170, 1],
      'lightskyblue': [135, 206, 250, 1], 'lightslategray': [119, 136, 153, 1],
      'lightslategrey': [119, 136, 153, 1], 'lightsteelblue': [176, 196, 222, 1],
      'lightyellow': [255, 255, 224, 1], 'lime': [0, 255, 0, 1],
      'limegreen': [50, 205, 50, 1], 'linen': [250, 240, 230, 1],
      'magenta': [255, 0, 255, 1], 'maroon': [128, 0, 0, 1],
      'mediumaquamarine': [102, 205, 170, 1], 'mediumblue': [0, 0, 205, 1],
      'mediumorchid': [186, 85, 211, 1], 'mediumpurple': [147, 112, 219, 1],
      'mediumseagreen': [60, 179, 113, 1], 'mediumslateblue': [123, 104, 238, 1],
      'mediumspringgreen': [0, 250, 154, 1], 'mediumturquoise': [72, 209, 204, 1],
      'mediumvioletred': [199, 21, 133, 1], 'midnightblue': [25, 25, 112, 1],
      'mintcream': [245, 255, 250, 1], 'mistyrose': [255, 228, 225, 1],
      'moccasin': [255, 228, 181, 1], 'navajowhite': [255, 222, 173, 1],
      'navy': [0, 0, 128, 1], 'oldlace': [253, 245, 230, 1],
      'olive': [128, 128, 0, 1], 'olivedrab': [107, 142, 35, 1],
      'orange': [255, 165, 0, 1], 'orangered': [255, 69, 0, 1],
      'orchid': [218, 112, 214, 1], 'palegoldenrod': [238, 232, 170, 1],
      'palegreen': [152, 251, 152, 1], 'paleturquoise': [175, 238, 238, 1],
      'palevioletred': [219, 112, 147, 1], 'papayawhip': [255, 239, 213, 1],
      'peachpuff': [255, 218, 185, 1], 'peru': [205, 133, 63, 1],
      'pink': [255, 192, 203, 1], 'plum': [221, 160, 221, 1],
      'powderblue': [176, 224, 230, 1], 'purple': [128, 0, 128, 1],
      'red': [255, 0, 0, 1], 'rosybrown': [188, 143, 143, 1],
      'royalblue': [65, 105, 225, 1], 'saddlebrown': [139, 69, 19, 1],
      'salmon': [250, 128, 114, 1], 'sandybrown': [244, 164, 96, 1],
      'seagreen': [46, 139, 87, 1], 'seashell': [255, 245, 238, 1],
      'sienna': [160, 82, 45, 1], 'silver': [192, 192, 192, 1],
      'skyblue': [135, 206, 235, 1], 'slateblue': [106, 90, 205, 1],
      'slategray': [112, 128, 144, 1], 'slategrey': [112, 128, 144, 1],
      'snow': [255, 250, 250, 1], 'springgreen': [0, 255, 127, 1],
      'steelblue': [70, 130, 180, 1], 'tan': [210, 180, 140, 1],
      'teal': [0, 128, 128, 1], 'thistle': [216, 191, 216, 1],
      'tomato': [255, 99, 71, 1], 'turquoise': [64, 224, 208, 1],
      'violet': [238, 130, 238, 1], 'wheat': [245, 222, 179, 1],
      'white': [255, 255, 255, 1], 'whitesmoke': [245, 245, 245, 1],
      'yellow': [255, 255, 0, 1], 'yellowgreen': [154, 205, 50, 1]
  };

  function clampCssByte(i) {  // Clamp to integer 0 .. 255.
      i = Math.round(i);  // Seems to be what Chrome does (vs truncation).
      return i < 0 ? 0 : i > 255 ? 255 : i;
  }

  function clampCssFloat(f) {  // Clamp to float 0.0 .. 1.0.
      return f < 0 ? 0 : f > 1 ? 1 : f;
  }

  function parseCssInt(str) {  // int or percentage.
      if (str.length && str.charAt(str.length - 1) === '%') {
          return clampCssByte(parseFloat(str) / 100 * 255);
      }
      return clampCssByte(parseInt(str, 10));
  }

  function parseCssFloat(str) {  // float or percentage.
      if (str.length && str.charAt(str.length - 1) === '%') {
          return clampCssFloat(parseFloat(str) / 100);
      }
      return clampCssFloat(parseFloat(str));
  }

  function cssHueToRgb(m1, m2, h) {
      if (h < 0) {
          h += 1;
      }
      else if (h > 1) {
          h -= 1;
      }

      if (h * 6 < 1) {
          return m1 + (m2 - m1) * h * 6;
      }
      if (h * 2 < 1) {
          return m2;
      }
      if (h * 3 < 2) {
          return m1 + (m2 - m1) * (2 / 3 - h) * 6;
      }
      return m1;
  }

  function setRgba(out, r, g, b, a) {
      out[0] = r; out[1] = g; out[2] = b; out[3] = a;
      return out;
  }
  function copyRgba(out, a) {
      out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; out[3] = a[3];
      return out;
  }

  var colorCache = new LRU(20);
  var lastRemovedArr = null;

  function putToCache(colorStr, rgbaArr) {
      // Reuse removed array
      if (lastRemovedArr) {
          copyRgba(lastRemovedArr, rgbaArr);
      }
      lastRemovedArr = colorCache.put(colorStr, lastRemovedArr || (rgbaArr.slice()));
  }

  /**
   * @param {string} colorStr
   * @param {Array.<number>} out
   * @return {Array.<number>}
   * @memberOf module:zrender/util/color
   */
  function parse(colorStr, rgbaArr) {
      if (!colorStr) {
          return;
      }
      rgbaArr = rgbaArr || [];

      var cached = colorCache.get(colorStr);
      if (cached) {
          return copyRgba(rgbaArr, cached);
      }

      // colorStr may be not string
      colorStr = colorStr + '';
      // Remove all whitespace, not compliant, but should just be more accepting.
      var str = colorStr.replace(/ /g, '').toLowerCase();

      // Color keywords (and transparent) lookup.
      if (str in kCSSColorTable) {
          copyRgba(rgbaArr, kCSSColorTable[str]);
          putToCache(colorStr, rgbaArr);
          return rgbaArr;
      }

      // #abc and #abc123 syntax.
      if (str.charAt(0) === '#') {
          if (str.length === 4) {
              var iv = parseInt(str.substr(1), 16);  // TODO(deanm): Stricter parsing.
              if (!(iv >= 0 && iv <= 0xfff)) {
                  setRgba(rgbaArr, 0, 0, 0, 1);
                  return;  // Covers NaN.
              }
              setRgba(rgbaArr,
                  ((iv & 0xf00) >> 4) | ((iv & 0xf00) >> 8),
                  (iv & 0xf0) | ((iv & 0xf0) >> 4),
                  (iv & 0xf) | ((iv & 0xf) << 4),
                  1
              );
              putToCache(colorStr, rgbaArr);
              return rgbaArr;
          }
          else if (str.length === 7) {
              var iv = parseInt(str.substr(1), 16);  // TODO(deanm): Stricter parsing.
              if (!(iv >= 0 && iv <= 0xffffff)) {
                  setRgba(rgbaArr, 0, 0, 0, 1);
                  return;  // Covers NaN.
              }
              setRgba(rgbaArr,
                  (iv & 0xff0000) >> 16,
                  (iv & 0xff00) >> 8,
                  iv & 0xff,
                  1
              );
              putToCache(colorStr, rgbaArr);
              return rgbaArr;
          }

          return;
      }
      var op = str.indexOf('(');
      var ep = str.indexOf(')');
      if (op !== -1 && ep + 1 === str.length) {
          var fname = str.substr(0, op);
          var params = str.substr(op + 1, ep - (op + 1)).split(',');
          var alpha = 1;  // To allow case fallthrough.
          switch (fname) {
              case 'rgba':
                  if (params.length !== 4) {
                      setRgba(rgbaArr, 0, 0, 0, 1);
                      return;
                  }
                  alpha = parseCssFloat(params.pop()); // jshint ignore:line
              // Fall through.
              case 'rgb':
                  if (params.length !== 3) {
                      setRgba(rgbaArr, 0, 0, 0, 1);
                      return;
                  }
                  setRgba(rgbaArr,
                      parseCssInt(params[0]),
                      parseCssInt(params[1]),
                      parseCssInt(params[2]),
                      alpha
                  );
                  putToCache(colorStr, rgbaArr);
                  return rgbaArr;
              case 'hsla':
                  if (params.length !== 4) {
                      setRgba(rgbaArr, 0, 0, 0, 1);
                      return;
                  }
                  params[3] = parseCssFloat(params[3]);
                  hsla2rgba(params, rgbaArr);
                  putToCache(colorStr, rgbaArr);
                  return rgbaArr;
              case 'hsl':
                  if (params.length !== 3) {
                      setRgba(rgbaArr, 0, 0, 0, 1);
                      return;
                  }
                  hsla2rgba(params, rgbaArr);
                  putToCache(colorStr, rgbaArr);
                  return rgbaArr;
              default:
                  return;
          }
      }

      setRgba(rgbaArr, 0, 0, 0, 1);
      return;
  }

  /**
   * @param {Array.<number>} hsla
   * @param {Array.<number>} rgba
   * @return {Array.<number>} rgba
   */
  function hsla2rgba(hsla, rgba) {
      var h = (((parseFloat(hsla[0]) % 360) + 360) % 360) / 360;  // 0 .. 1
      // NOTE(deanm): According to the CSS spec s/l should only be
      // percentages, but we don't bother and let float or percentage.
      var s = parseCssFloat(hsla[1]);
      var l = parseCssFloat(hsla[2]);
      var m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s;
      var m1 = l * 2 - m2;

      rgba = rgba || [];
      setRgba(rgba,
          clampCssByte(cssHueToRgb(m1, m2, h + 1 / 3) * 255),
          clampCssByte(cssHueToRgb(m1, m2, h) * 255),
          clampCssByte(cssHueToRgb(m1, m2, h - 1 / 3) * 255),
          1
      );

      if (hsla.length === 4) {
          rgba[3] = hsla[3];
      }

      return rgba;
  }

  /**
   * @module echarts/animation/Animator
   */

  var arraySlice = Array.prototype.slice;

  function defaultGetter(target, key) {
      return target[key];
  }

  function defaultSetter(target, key, value) {
      target[key] = value;
  }

  /**
   * @param  {number} p0
   * @param  {number} p1
   * @param  {number} percent
   * @return {number}
   */
  function interpolateNumber(p0, p1, percent) {
      return (p1 - p0) * percent + p0;
  }

  /**
   * @param  {string} p0
   * @param  {string} p1
   * @param  {number} percent
   * @return {string}
   */
  function interpolateString(p0, p1, percent) {
      return percent > 0.5 ? p1 : p0;
  }

  /**
   * @param  {Array} p0
   * @param  {Array} p1
   * @param  {number} percent
   * @param  {Array} out
   * @param  {number} arrDim
   */
  function interpolateArray(p0, p1, percent, out, arrDim) {
      var len = p0.length;
      if (arrDim === 1) {
          for (var i = 0; i < len; i++) {
              out[i] = interpolateNumber(p0[i], p1[i], percent);
          }
      }
      else {
          var len2 = len && p0[0].length;
          for (var i = 0; i < len; i++) {
              for (var j = 0; j < len2; j++) {
                  out[i][j] = interpolateNumber(
                      p0[i][j], p1[i][j], percent
                  );
              }
          }
      }
  }

  // arr0 is source array, arr1 is target array.
  // Do some preprocess to avoid error happened when interpolating from arr0 to arr1
  function fillArr(arr0, arr1, arrDim) {
      var arr0Len = arr0.length;
      var arr1Len = arr1.length;
      if (arr0Len !== arr1Len) {
          // FIXME Not work for TypedArray
          var isPreviousLarger = arr0Len > arr1Len;
          if (isPreviousLarger) {
              // Cut the previous
              arr0.length = arr1Len;
          }
          else {
              // Fill the previous
              for (var i = arr0Len; i < arr1Len; i++) {
                  arr0.push(
                      arrDim === 1 ? arr1[i] : arraySlice.call(arr1[i])
                  );
              }
          }
      }
      // Handling NaN value
      var len2 = arr0[0] && arr0[0].length;
      for (var i = 0; i < arr0.length; i++) {
          if (arrDim === 1) {
              if (isNaN(arr0[i])) {
                  arr0[i] = arr1[i];
              }
          }
          else {
              for (var j = 0; j < len2; j++) {
                  if (isNaN(arr0[i][j])) {
                      arr0[i][j] = arr1[i][j];
                  }
              }
          }
      }
  }

  /**
   * @param  {Array} arr0
   * @param  {Array} arr1
   * @param  {number} arrDim
   * @return {boolean}
   */
  function isArraySame(arr0, arr1, arrDim) {
      if (arr0 === arr1) {
          return true;
      }
      var len = arr0.length;
      if (len !== arr1.length) {
          return false;
      }
      if (arrDim === 1) {
          for (var i = 0; i < len; i++) {
              if (arr0[i] !== arr1[i]) {
                  return false;
              }
          }
      }
      else {
          var len2 = arr0[0].length;
          for (var i = 0; i < len; i++) {
              for (var j = 0; j < len2; j++) {
                  if (arr0[i][j] !== arr1[i][j]) {
                      return false;
                  }
              }
          }
      }
      return true;
  }

  /**
   * Catmull Rom interpolate array
   * @param  {Array} p0
   * @param  {Array} p1
   * @param  {Array} p2
   * @param  {Array} p3
   * @param  {number} t
   * @param  {number} t2
   * @param  {number} t3
   * @param  {Array} out
   * @param  {number} arrDim
   */
  function catmullRomInterpolateArray(
      p0, p1, p2, p3, t, t2, t3, out, arrDim
  ) {
      var len = p0.length;
      if (arrDim === 1) {
          for (var i = 0; i < len; i++) {
              out[i] = catmullRomInterpolate(
                  p0[i], p1[i], p2[i], p3[i], t, t2, t3
              );
          }
      }
      else {
          var len2 = p0[0].length;
          for (var i = 0; i < len; i++) {
              for (var j = 0; j < len2; j++) {
                  out[i][j] = catmullRomInterpolate(
                      p0[i][j], p1[i][j], p2[i][j], p3[i][j],
                      t, t2, t3
                  );
              }
          }
      }
  }

  /**
   * Catmull Rom interpolate number
   * @param  {number} p0
   * @param  {number} p1
   * @param  {number} p2
   * @param  {number} p3
   * @param  {number} t
   * @param  {number} t2
   * @param  {number} t3
   * @return {number}
   */
  function catmullRomInterpolate(p0, p1, p2, p3, t, t2, t3) {
      var v0 = (p2 - p0) * 0.5;
      var v1 = (p3 - p1) * 0.5;
      return (2 * (p1 - p2) + v0 + v1) * t3
              + (-3 * (p1 - p2) - 2 * v0 - v1) * t2
              + v0 * t + p1;
  }

  function cloneValue(value) {
      if (isArrayLike(value)) {
          var len = value.length;
          if (isArrayLike(value[0])) {
              var ret = [];
              for (var i = 0; i < len; i++) {
                  ret.push(arraySlice.call(value[i]));
              }
              return ret;
          }

          return arraySlice.call(value);
      }

      return value;
  }

  function rgba2String(rgba) {
      rgba[0] = Math.floor(rgba[0]);
      rgba[1] = Math.floor(rgba[1]);
      rgba[2] = Math.floor(rgba[2]);

      return 'rgba(' + rgba.join(',') + ')';
  }

  function getArrayDim(keyframes) {
      var lastValue = keyframes[keyframes.length - 1].value;
      return isArrayLike(lastValue && lastValue[0]) ? 2 : 1;
  }

  function createTrackClip(animator, easing, oneTrackDone, keyframes, propName, forceAnimate) {
      var getter = animator._getter;
      var setter = animator._setter;
      var useSpline = easing === 'spline';

      var trackLen = keyframes.length;
      if (!trackLen) {
          return;
      }
      // Guess data type
      var firstVal = keyframes[0].value;
      var isValueArray = isArrayLike(firstVal);
      var isValueColor = false;
      var isValueString = false;

      // For vertices morphing
      var arrDim = isValueArray ? getArrayDim(keyframes) : 0;

      var trackMaxTime;
      // Sort keyframe as ascending
      keyframes.sort(function (a, b) {
          return a.time - b.time;
      });

      trackMaxTime = keyframes[trackLen - 1].time;
      // Percents of each keyframe
      var kfPercents = [];
      // Value of each keyframe
      var kfValues = [];
      var prevValue = keyframes[0].value;
      var isAllValueEqual = true;
      for (var i = 0; i < trackLen; i++) {
          kfPercents.push(keyframes[i].time / trackMaxTime);
          // Assume value is a color when it is a string
          var value = keyframes[i].value;

          // Check if value is equal, deep check if value is array
          if (!((isValueArray && isArraySame(value, prevValue, arrDim))
              || (!isValueArray && value === prevValue))) {
              isAllValueEqual = false;
          }
          prevValue = value;

          // Try converting a string to a color array
          if (typeof value === 'string') {
              var colorArray = parse(value);
              if (colorArray) {
                  value = colorArray;
                  isValueColor = true;
              }
              else {
                  isValueString = true;
              }
          }
          kfValues.push(value);
      }
      if (!forceAnimate && isAllValueEqual) {
          return;
      }

      var lastValue = kfValues[trackLen - 1];
      // Polyfill array and NaN value
      for (var i = 0; i < trackLen - 1; i++) {
          if (isValueArray) {
              fillArr(kfValues[i], lastValue, arrDim);
          }
          else {
              if (isNaN(kfValues[i]) && !isNaN(lastValue) && !isValueString && !isValueColor) {
                  kfValues[i] = lastValue;
              }
          }
      }
      isValueArray && fillArr(getter(animator._target, propName), lastValue, arrDim);

      // Cache the key of last frame to speed up when
      // animation playback is sequency
      var lastFrame = 0;
      var lastFramePercent = 0;
      var start;
      var w;
      var p0;
      var p1;
      var p2;
      var p3;

      if (isValueColor) {
          var rgba = [0, 0, 0, 0];
      }

      var onframe = function (target, percent) {
          // Find the range keyframes
          // kf1-----kf2---------current--------kf3
          // find kf2 and kf3 and do interpolation
          var frame;
          // In the easing function like elasticOut, percent may less than 0
          if (percent < 0) {
              frame = 0;
          }
          else if (percent < lastFramePercent) {
              // Start from next key
              // PENDING start from lastFrame ?
              start = Math.min(lastFrame + 1, trackLen - 1);
              for (frame = start; frame >= 0; frame--) {
                  if (kfPercents[frame] <= percent) {
                      break;
                  }
              }
              // PENDING really need to do this ?
              frame = Math.min(frame, trackLen - 2);
          }
          else {
              for (frame = lastFrame; frame < trackLen; frame++) {
                  if (kfPercents[frame] > percent) {
                      break;
                  }
              }
              frame = Math.min(frame - 1, trackLen - 2);
          }
          lastFrame = frame;
          lastFramePercent = percent;

          var range = (kfPercents[frame + 1] - kfPercents[frame]);
          if (range === 0) {
              return;
          }
          else {
              w = (percent - kfPercents[frame]) / range;
          }
          if (useSpline) {
              p1 = kfValues[frame];
              p0 = kfValues[frame === 0 ? frame : frame - 1];
              p2 = kfValues[frame > trackLen - 2 ? trackLen - 1 : frame + 1];
              p3 = kfValues[frame > trackLen - 3 ? trackLen - 1 : frame + 2];
              if (isValueArray) {
                  catmullRomInterpolateArray(
                      p0, p1, p2, p3, w, w * w, w * w * w,
                      getter(target, propName),
                      arrDim
                  );
              }
              else {
                  var value;
                  if (isValueColor) {
                      value = catmullRomInterpolateArray(
                          p0, p1, p2, p3, w, w * w, w * w * w,
                          rgba, 1
                      );
                      value = rgba2String(rgba);
                  }
                  else if (isValueString) {
                      // String is step(0.5)
                      return interpolateString(p1, p2, w);
                  }
                  else {
                      value = catmullRomInterpolate(
                          p0, p1, p2, p3, w, w * w, w * w * w
                      );
                  }
                  setter(
                      target,
                      propName,
                      value
                  );
              }
          }
          else {
              if (isValueArray) {
                  interpolateArray(
                      kfValues[frame], kfValues[frame + 1], w,
                      getter(target, propName),
                      arrDim
                  );
              }
              else {
                  var value;
                  if (isValueColor) {
                      interpolateArray(
                          kfValues[frame], kfValues[frame + 1], w,
                          rgba, 1
                      );
                      value = rgba2String(rgba);
                  }
                  else if (isValueString) {
                      // String is step(0.5)
                      return interpolateString(kfValues[frame], kfValues[frame + 1], w);
                  }
                  else {
                      value = interpolateNumber(kfValues[frame], kfValues[frame + 1], w);
                  }
                  setter(
                      target,
                      propName,
                      value
                  );
              }
          }
      };

      var clip = new Clip({
          target: animator._target,
          life: trackMaxTime,
          loop: animator._loop,
          delay: animator._delay,
          onframe: onframe,
          ondestroy: oneTrackDone
      });

      if (easing && easing !== 'spline') {
          clip.easing = easing;
      }

      return clip;
  }

  /**
   * @alias module:zrender/animation/Animator
   * @constructor
   * @param {Object} target
   * @param {boolean} loop
   * @param {Function} getter
   * @param {Function} setter
   */
  var Animator = function (target, loop, getter, setter) {
      this._tracks = {};
      this._target = target;

      this._loop = loop || false;

      this._getter = getter || defaultGetter;
      this._setter = setter || defaultSetter;

      this._clipCount = 0;

      this._delay = 0;

      this._doneList = [];

      this._onframeList = [];

      this._clipList = [];
  };

  Animator.prototype = {
      /**
       * 设置动画关键帧
       * @param  {number} time 关键帧时间，单位是ms
       * @param  {Object} props 关键帧的属性值，key-value表示
       * @return {module:zrender/animation/Animator}
       */
      when: function (time /* ms */, props) {
          var tracks = this._tracks;
          for (var propName in props) {
              if (!props.hasOwnProperty(propName)) {
                  continue;
              }

              if (!tracks[propName]) {
                  tracks[propName] = [];
                  // Invalid value
                  var value = this._getter(this._target, propName);
                  if (value == null) {
                      // zrLog('Invalid property ' + propName);
                      continue;
                  }
                  // If time is 0
                  //  Then props is given initialize value
                  // Else
                  //  Initialize value from current prop value
                  if (time !== 0) {
                      tracks[propName].push({
                          time: 0,
                          value: cloneValue(value)
                      });
                  }
              }
              tracks[propName].push({
                  time: time,
                  value: props[propName]
              });
          }
          return this;
      },
      /**
       * 添加动画每一帧的回调函数
       * @param  {Function} callback
       * @return {module:zrender/animation/Animator}
       */
      during: function (callback) {
          this._onframeList.push(callback);
          return this;
      },

      pause: function () {
          for (var i = 0; i < this._clipList.length; i++) {
              this._clipList[i].pause();
          }
          this._paused = true;
      },

      resume: function () {
          for (var i = 0; i < this._clipList.length; i++) {
              this._clipList[i].resume();
          }
          this._paused = false;
      },

      isPaused: function () {
          return !!this._paused;
      },

      _doneCallback: function () {
          // Clear all tracks
          this._tracks = {};
          // Clear all clips
          this._clipList.length = 0;

          var doneList = this._doneList;
          var len = doneList.length;
          for (var i = 0; i < len; i++) {
              doneList[i].call(this);
          }
      },
      /**
       * 开始执行动画
       * @param  {string|Function} [easing]
       *         动画缓动函数，详见{@link module:zrender/animation/easing}
       * @param  {boolean} forceAnimate
       * @return {module:zrender/animation/Animator}
       */
      start: function (easing, forceAnimate) {

          var self = this;
          var clipCount = 0;

          var oneTrackDone = function () {
              clipCount--;
              if (!clipCount) {
                  self._doneCallback();
              }
          };

          var lastClip;
          for (var propName in this._tracks) {
              if (!this._tracks.hasOwnProperty(propName)) {
                  continue;
              }
              var clip = createTrackClip(
                  this, easing, oneTrackDone,
                  this._tracks[propName], propName, forceAnimate
              );
              if (clip) {
                  this._clipList.push(clip);
                  clipCount++;

                  // If start after added to animation
                  if (this.animation) {
                      this.animation.addClip(clip);
                  }

                  lastClip = clip;
              }
          }

          // Add during callback on the last clip
          if (lastClip) {
              var oldOnFrame = lastClip.onframe;
              lastClip.onframe = function (target, percent) {
                  oldOnFrame(target, percent);

                  for (var i = 0; i < self._onframeList.length; i++) {
                      self._onframeList[i](target, percent);
                  }
              };
          }

          // This optimization will help the case that in the upper application
          // the view may be refreshed frequently, where animation will be
          // called repeatly but nothing changed.
          if (!clipCount) {
              this._doneCallback();
          }
          return this;
      },
      /**
       * 停止动画
       * @param {boolean} forwardToLast If move to last frame before stop
       */
      stop: function (forwardToLast) {
          var clipList = this._clipList;
          var animation = this.animation;
          for (var i = 0; i < clipList.length; i++) {
              var clip = clipList[i];
              if (forwardToLast) {
                  // Move to last frame before stop
                  clip.onframe(this._target, 1);
              }
              animation && animation.removeClip(clip);
          }
          clipList.length = 0;
      },
      /**
       * 设置动画延迟开始的时间
       * @param  {number} time 单位ms
       * @return {module:zrender/animation/Animator}
       */
      delay: function (time) {
          this._delay = time;
          return this;
      },
      /**
       * 添加动画结束的回调
       * @param  {Function} cb
       * @return {module:zrender/animation/Animator}
       */
      done: function (cb) {
          if (cb) {
              this._doneList.push(cb);
          }
          return this;
      },

      /**
       * @return {Array.<module:zrender/animation/Clip>}
       */
      getClips: function () {
          return this._clipList;
      }
  };

  var dpr = 1;

  // If in browser environment
  if (typeof window !== 'undefined') {
      dpr = Math.max(window.devicePixelRatio || 1, 1);
  }

  // retina 屏幕优化
  var devicePixelRatio = dpr;

  var log = function () {
  };

  /**
   * @alias modue:zrender/mixin/Animatable
   * @constructor
   */
  var Animatable = function () {

      /**
       * @type {Array.<module:zrender/animation/Animator>}
       * @readOnly
       */
      this.animators = [];
  };

  Animatable.prototype = {

      constructor: Animatable,

      /**
       * 动画
       *
       * @param {string} path The path to fetch value from object, like 'a.b.c'.
       * @param {boolean} [loop] Whether to loop animation.
       * @return {module:zrender/animation/Animator}
       * @example:
       *     el.animate('style', false)
       *         .when(1000, {x: 10} )
       *         .done(function(){ // Animation done })
       *         .start()
       */
      animate: function (path, loop) {
          var target;
          var animatingShape = false;
          var el = this;
          var zr = this.__zr;
          if (path) {
              var pathSplitted = path.split('.');
              var prop = el;
              // If animating shape
              animatingShape = pathSplitted[0] === 'shape';
              for (var i = 0, l = pathSplitted.length; i < l; i++) {
                  if (!prop) {
                      continue;
                  }
                  prop = prop[pathSplitted[i]];
              }
              if (prop) {
                  target = prop;
              }
          }
          else {
              target = el;
          }

          if (!target) {
              log(
                  'Property "'
                  + path
                  + '" is not existed in element '
                  + el.id
              );
              return;
          }

          var animators = el.animators;

          var animator = new Animator(target, loop);

          animator.during(function (target) {
              el.dirty(animatingShape);
          })
          .done(function () {
              // FIXME Animator will not be removed if use `Animator#stop` to stop animation
              animators.splice(indexOf(animators, animator), 1);
          });

          animators.push(animator);

          // If animate after added to the zrender
          if (zr) {
              zr.animation.addAnimator(animator);
          }

          return animator;
      },

      /**
       * 停止动画
       * @param {boolean} forwardToLast If move to last frame before stop
       */
      stopAnimation: function (forwardToLast) {
          var animators = this.animators;
          var len = animators.length;
          for (var i = 0; i < len; i++) {
              animators[i].stop(forwardToLast);
          }
          animators.length = 0;

          return this;
      },

      /**
       * Caution: this method will stop previous animation.
       * So do not use this method to one element twice before
       * animation starts, unless you know what you are doing.
       * @param {Object} target
       * @param {number} [time=500] Time in ms
       * @param {string} [easing='linear']
       * @param {number} [delay=0]
       * @param {Function} [callback]
       * @param {Function} [forceAnimate] Prevent stop animation and callback
       *        immediently when target values are the same as current values.
       *
       * @example
       *  // Animate position
       *  el.animateTo({
       *      position: [10, 10]
       *  }, function () { // done })
       *
       *  // Animate shape, style and position in 100ms, delayed 100ms, with cubicOut easing
       *  el.animateTo({
       *      shape: {
       *          width: 500
       *      },
       *      style: {
       *          fill: 'red'
       *      }
       *      position: [10, 10]
       *  }, 100, 100, 'cubicOut', function () { // done })
       */
      // TODO Return animation key
      animateTo: function (target, time, delay, easing, callback, forceAnimate) {
          animateTo(this, target, time, delay, easing, callback, forceAnimate);
      },

      /**
       * Animate from the target state to current state.
       * The params and the return value are the same as `this.animateTo`.
       */
      animateFrom: function (target, time, delay, easing, callback, forceAnimate) {
          animateTo(this, target, time, delay, easing, callback, forceAnimate, true);
      }
  };

  function animateTo(animatable, target, time, delay, easing, callback, forceAnimate, reverse) {
      // animateTo(target, time, easing, callback);
      if (isString(delay)) {
          callback = easing;
          easing = delay;
          delay = 0;
      }
      // animateTo(target, time, delay, callback);
      else if (isFunction(easing)) {
          callback = easing;
          easing = 'linear';
          delay = 0;
      }
      // animateTo(target, time, callback);
      else if (isFunction(delay)) {
          callback = delay;
          delay = 0;
      }
      // animateTo(target, callback)
      else if (isFunction(time)) {
          callback = time;
          time = 500;
      }
      // animateTo(target)
      else if (!time) {
          time = 500;
      }
      // Stop all previous animations
      animatable.stopAnimation();
      animateToShallow(animatable, '', animatable, target, time, delay, reverse);

      // Animators may be removed immediately after start
      // if there is nothing to animate
      var animators = animatable.animators.slice();
      var count = animators.length;
      function done() {
          count--;
          if (!count) {
              callback && callback();
          }
      }

      // No animators. This should be checked before animators[i].start(),
      // because 'done' may be executed immediately if no need to animate.
      if (!count) {
          callback && callback();
      }
      // Start after all animators created
      // Incase any animator is done immediately when all animation properties are not changed
      for (var i = 0; i < animators.length; i++) {
          animators[i]
              .done(done)
              .start(easing, forceAnimate);
      }
  }

  /**
   * @param {string} path=''
   * @param {Object} source=animatable
   * @param {Object} target
   * @param {number} [time=500]
   * @param {number} [delay=0]
   * @param {boolean} [reverse] If `true`, animate
   *        from the `target` to current state.
   *
   * @example
   *  // Animate position
   *  el._animateToShallow({
   *      position: [10, 10]
   *  })
   *
   *  // Animate shape, style and position in 100ms, delayed 100ms
   *  el._animateToShallow({
   *      shape: {
   *          width: 500
   *      },
   *      style: {
   *          fill: 'red'
   *      }
   *      position: [10, 10]
   *  }, 100, 100)
   */
  function animateToShallow(animatable, path, source, target, time, delay, reverse) {
      var objShallow = {};
      var propertyCount = 0;
      for (var name in target) {
          if (!target.hasOwnProperty(name)) {
              continue;
          }

          if (source[name] != null) {
              if (isObject(target[name]) && !isArrayLike(target[name])) {
                  animateToShallow(
                      animatable,
                      path ? path + '.' + name : name,
                      source[name],
                      target[name],
                      time,
                      delay,
                      reverse
                  );
              }
              else {
                  if (reverse) {
                      objShallow[name] = source[name];
                      setAttrByPath(animatable, path, name, target[name]);
                  }
                  else {
                      objShallow[name] = target[name];
                  }
                  propertyCount++;
              }
          }
          else if (target[name] != null && !reverse) {
              setAttrByPath(animatable, path, name, target[name]);
          }
      }

      if (propertyCount > 0) {
          animatable.animate(path, false)
              .when(time == null ? 500 : time, objShallow)
              .delay(delay || 0);
      }
  }

  function setAttrByPath(el, path, name, value) {
      // Attr directly if not has property
      // FIXME, if some property not needed for element ?
      if (!path) {
          el.attr(name, value);
      }
      else {
          // Only support set shape or style
          var props = {};
          props[path] = {};
          props[path][name] = value;
          el.attr(props);
      }
  }

  /**
   * @alias module:zrender/Element
   * @constructor
   * @extends {module:zrender/mixin/Animatable}
   * @extends {module:zrender/mixin/Transformable}
   * @extends {module:zrender/mixin/Eventful}
   */
  var Element = function (opts) { // jshint ignore:line

      Transformable.call(this, opts);
      Eventful.call(this, opts);
      Animatable.call(this, opts);

      /**
       * 画布元素ID
       * @type {string}
       */
      this.id = opts.id || guid();
  };

  Element.prototype = {

      /**
       * 元素类型
       * Element type
       * @type {string}
       */
      type: 'element',

      /**
       * 元素名字
       * Element name
       * @type {string}
       */
      name: '',

      /**
       * ZRender 实例对象，会在 element 添加到 zrender 实例中后自动赋值
       * ZRender instance will be assigned when element is associated with zrender
       * @name module:/zrender/Element#__zr
       * @type {module:zrender/ZRender}
       */
      __zr: null,

      /**
       * 图形是否忽略，为true时忽略图形的绘制以及事件触发
       * If ignore drawing and events of the element object
       * @name module:/zrender/Element#ignore
       * @type {boolean}
       * @default false
       */
      ignore: false,

      /**
       * 用于裁剪的路径(shape)，所有 Group 内的路径在绘制时都会被这个路径裁剪
       * 该路径会继承被裁减对象的变换
       * @type {module:zrender/graphic/Path}
       * @see http://www.w3.org/TR/2dcontext/#clipping-region
       * @readOnly
       */
      clipPath: null,

      /**
       * 是否是 Group
       * @type {boolean}
       */
      isGroup: false,

      /**
       * Drift element
       * @param  {number} dx dx on the global space
       * @param  {number} dy dy on the global space
       */
      drift: function (dx, dy) {
          switch (this.draggable) {
              case 'horizontal':
                  dy = 0;
                  break;
              case 'vertical':
                  dx = 0;
                  break;
          }

          var m = this.transform;
          if (!m) {
              m = this.transform = [1, 0, 0, 1, 0, 0];
          }
          m[4] += dx;
          m[5] += dy;

          this.decomposeTransform();
          this.dirty(false);
      },

      /**
       * Hook before update
       */
      beforeUpdate: function () {},
      /**
       * Hook after update
       */
      afterUpdate: function () {},
      /**
       * Update each frame
       */
      update: function () {
          this.updateTransform();
      },

      /**
       * @param  {Function} cb
       * @param  {}   context
       */
      traverse: function (cb, context) {},

      /**
       * @protected
       */
      attrKV: function (key, value) {
          if (key === 'position' || key === 'scale' || key === 'origin') {
              // Copy the array
              if (value) {
                  var target = this[key];
                  if (!target) {
                      target = this[key] = [];
                  }
                  target[0] = value[0];
                  target[1] = value[1];
              }
          }
          else {
              this[key] = value;
          }
      },

      /**
       * Hide the element
       */
      hide: function () {
          this.ignore = true;
          this.__zr && this.__zr.refresh();
      },

      /**
       * Show the element
       */
      show: function () {
          this.ignore = false;
          this.__zr && this.__zr.refresh();
      },

      /**
       * @param {string|Object} key
       * @param {*} value
       */
      attr: function (key, value) {
          if (typeof key === 'string') {
              this.attrKV(key, value);
          }
          else if (isObject(key)) {
              for (var name in key) {
                  if (key.hasOwnProperty(name)) {
                      this.attrKV(name, key[name]);
                  }
              }
          }

          this.dirty(false);

          return this;
      },

      /**
       * @param {module:zrender/graphic/Path} clipPath
       */
      setClipPath: function (clipPath) {
          var zr = this.__zr;
          if (zr) {
              clipPath.addSelfToZr(zr);
          }

          // Remove previous clip path
          if (this.clipPath && this.clipPath !== clipPath) {
              this.removeClipPath();
          }

          this.clipPath = clipPath;
          clipPath.__zr = zr;
          clipPath.__clipTarget = this;

          this.dirty(false);
      },

      /**
       */
      removeClipPath: function () {
          var clipPath = this.clipPath;
          if (clipPath) {
              if (clipPath.__zr) {
                  clipPath.removeSelfFromZr(clipPath.__zr);
              }

              clipPath.__zr = null;
              clipPath.__clipTarget = null;
              this.clipPath = null;

              this.dirty(false);
          }
      },

      /**
       * Add self from zrender instance.
       * Not recursively because it will be invoked when element added to storage.
       * @param {module:zrender/ZRender} zr
       */
      addSelfToZr: function (zr) {
          this.__zr = zr;
          // 添加动画
          var animators = this.animators;
          if (animators) {
              for (var i = 0; i < animators.length; i++) {
                  zr.animation.addAnimator(animators[i]);
              }
          }

          if (this.clipPath) {
              this.clipPath.addSelfToZr(zr);
          }
      },

      /**
       * Remove self from zrender instance.
       * Not recursively because it will be invoked when element added to storage.
       * @param {module:zrender/ZRender} zr
       */
      removeSelfFromZr: function (zr) {
          this.__zr = null;
          // 移除动画
          var animators = this.animators;
          if (animators) {
              for (var i = 0; i < animators.length; i++) {
                  zr.animation.removeAnimator(animators[i]);
              }
          }

          if (this.clipPath) {
              this.clipPath.removeSelfFromZr(zr);
          }
      }
  };

  mixin(Element, Animatable);
  mixin(Element, Transformable);
  mixin(Element, Eventful);

  /**
   * @module echarts/core/BoundingRect
   */

  var v2ApplyTransform = applyTransform;
  var mathMin = Math.min;
  var mathMax = Math.max;

  /**
   * @alias module:echarts/core/BoundingRect
   */
  function BoundingRect(x, y, width, height) {

      if (width < 0) {
          x = x + width;
          width = -width;
      }
      if (height < 0) {
          y = y + height;
          height = -height;
      }

      /**
       * @type {number}
       */
      this.x = x;
      /**
       * @type {number}
       */
      this.y = y;
      /**
       * @type {number}
       */
      this.width = width;
      /**
       * @type {number}
       */
      this.height = height;
  }

  BoundingRect.prototype = {

      constructor: BoundingRect,

      /**
       * @param {module:echarts/core/BoundingRect} other
       */
      union: function (other) {
          var x = mathMin(other.x, this.x);
          var y = mathMin(other.y, this.y);

          this.width = mathMax(
                  other.x + other.width,
                  this.x + this.width
              ) - x;
          this.height = mathMax(
                  other.y + other.height,
                  this.y + this.height
              ) - y;
          this.x = x;
          this.y = y;
      },

      /**
       * @param {Array.<number>} m
       * @methods
       */
      applyTransform: (function () {
          var lt = [];
          var rb = [];
          var lb = [];
          var rt = [];
          return function (m) {
              // In case usage like this
              // el.getBoundingRect().applyTransform(el.transform)
              // And element has no transform
              if (!m) {
                  return;
              }
              lt[0] = lb[0] = this.x;
              lt[1] = rt[1] = this.y;
              rb[0] = rt[0] = this.x + this.width;
              rb[1] = lb[1] = this.y + this.height;

              v2ApplyTransform(lt, lt, m);
              v2ApplyTransform(rb, rb, m);
              v2ApplyTransform(lb, lb, m);
              v2ApplyTransform(rt, rt, m);

              this.x = mathMin(lt[0], rb[0], lb[0], rt[0]);
              this.y = mathMin(lt[1], rb[1], lb[1], rt[1]);
              var maxX = mathMax(lt[0], rb[0], lb[0], rt[0]);
              var maxY = mathMax(lt[1], rb[1], lb[1], rt[1]);
              this.width = maxX - this.x;
              this.height = maxY - this.y;
          };
      })(),

      /**
       * Calculate matrix of transforming from self to target rect
       * @param  {module:zrender/core/BoundingRect} b
       * @return {Array.<number>}
       */
      calculateTransform: function (b) {
          var a = this;
          var sx = b.width / a.width;
          var sy = b.height / a.height;

          var m = create();

          // 矩阵右乘
          translate(m, m, [-a.x, -a.y]);
          scale(m, m, [sx, sy]);
          translate(m, m, [b.x, b.y]);

          return m;
      },

      /**
       * @param {(module:echarts/core/BoundingRect|Object)} b
       * @return {boolean}
       */
      intersect: function (b) {
          if (!b) {
              return false;
          }

          if (!(b instanceof BoundingRect)) {
              // Normalize negative width/height.
              b = BoundingRect.create(b);
          }

          var a = this;
          var ax0 = a.x;
          var ax1 = a.x + a.width;
          var ay0 = a.y;
          var ay1 = a.y + a.height;

          var bx0 = b.x;
          var bx1 = b.x + b.width;
          var by0 = b.y;
          var by1 = b.y + b.height;

          return !(ax1 < bx0 || bx1 < ax0 || ay1 < by0 || by1 < ay0);
      },

      contain: function (x, y) {
          var rect = this;
          return x >= rect.x
              && x <= (rect.x + rect.width)
              && y >= rect.y
              && y <= (rect.y + rect.height);
      },

      /**
       * @return {module:echarts/core/BoundingRect}
       */
      clone: function () {
          return new BoundingRect(this.x, this.y, this.width, this.height);
      },

      /**
       * Copy from another rect
       */
      copy: function (other) {
          this.x = other.x;
          this.y = other.y;
          this.width = other.width;
          this.height = other.height;
      },

      plain: function () {
          return {
              x: this.x,
              y: this.y,
              width: this.width,
              height: this.height
          };
      }
  };

  /**
   * @param {Object|module:zrender/core/BoundingRect} rect
   * @param {number} rect.x
   * @param {number} rect.y
   * @param {number} rect.width
   * @param {number} rect.height
   * @return {module:zrender/core/BoundingRect}
   */
  BoundingRect.create = function (rect) {
      return new BoundingRect(rect.x, rect.y, rect.width, rect.height);
  };

  var globalImageCache = new LRU(50);

  /**
   * @param {string|HTMLImageElement|HTMLCanvasElement|Canvas} newImageOrSrc
   * @return {HTMLImageElement|HTMLCanvasElement|Canvas} image
   */
  function findExistImage(newImageOrSrc) {
      if (typeof newImageOrSrc === 'string') {
          var cachedImgObj = globalImageCache.get(newImageOrSrc);
          return cachedImgObj && cachedImgObj.image;
      }
      else {
          return newImageOrSrc;
      }
  }

  /**
   * Caution: User should cache loaded images, but not just count on LRU.
   * Consider if required images more than LRU size, will dead loop occur?
   *
   * @param {string|HTMLImageElement|HTMLCanvasElement|Canvas} newImageOrSrc
   * @param {HTMLImageElement|HTMLCanvasElement|Canvas} image Existent image.
   * @param {module:zrender/Element} [hostEl] For calling `dirty`.
   * @param {Function} [cb] params: (image, cbPayload)
   * @param {Object} [cbPayload] Payload on cb calling.
   * @return {HTMLImageElement|HTMLCanvasElement|Canvas} image
   */
  function createOrUpdateImage(newImageOrSrc, image, hostEl, cb, cbPayload) {
      if (!newImageOrSrc) {
          return image;
      }
      else if (typeof newImageOrSrc === 'string') {

          // Image should not be loaded repeatly.
          if ((image && image.__zrImageSrc === newImageOrSrc) || !hostEl) {
              return image;
          }

          // Only when there is no existent image or existent image src
          // is different, this method is responsible for load.
          var cachedImgObj = globalImageCache.get(newImageOrSrc);

          var pendingWrap = {hostEl: hostEl, cb: cb, cbPayload: cbPayload};

          if (cachedImgObj) {
              image = cachedImgObj.image;
              !isImageReady(image) && cachedImgObj.pending.push(pendingWrap);
          }
          else {
              image = new Image();
              image.onload = image.onerror = imageOnLoad;

              globalImageCache.put(
                  newImageOrSrc,
                  image.__cachedImgObj = {
                      image: image,
                      pending: [pendingWrap]
                  }
              );

              image.src = image.__zrImageSrc = newImageOrSrc;
          }

          return image;
      }
      // newImageOrSrc is an HTMLImageElement or HTMLCanvasElement or Canvas
      else {
          return newImageOrSrc;
      }
  }

  function imageOnLoad() {
      var cachedImgObj = this.__cachedImgObj;
      this.onload = this.onerror = this.__cachedImgObj = null;

      for (var i = 0; i < cachedImgObj.pending.length; i++) {
          var pendingWrap = cachedImgObj.pending[i];
          var cb = pendingWrap.cb;
          cb && cb(this, pendingWrap.cbPayload);
          pendingWrap.hostEl.dirty();
      }
      cachedImgObj.pending.length = 0;
  }

  function isImageReady(image) {
      return image && image.width && image.height;
  }

  var textWidthCache = {};
  var textWidthCacheCounter = 0;

  var TEXT_CACHE_MAX = 5000;
  var STYLE_REG = /\{([a-zA-Z0-9_]+)\|([^}]*)\}/g;

  var DEFAULT_FONT = '12px sans-serif';

  // Avoid assign to an exported variable, for transforming to cjs.
  var methods$1 = {};

  /**
   * @public
   * @param {string} text
   * @param {string} font
   * @return {number} width
   */
  function getWidth(text, font) {
      font = font || DEFAULT_FONT;
      var key = text + ':' + font;
      if (textWidthCache[key]) {
          return textWidthCache[key];
      }

      var textLines = (text + '').split('\n');
      var width = 0;

      for (var i = 0, l = textLines.length; i < l; i++) {
          // textContain.measureText may be overrided in SVG or VML
          width = Math.max(measureText(textLines[i], font).width, width);
      }

      if (textWidthCacheCounter > TEXT_CACHE_MAX) {
          textWidthCacheCounter = 0;
          textWidthCache = {};
      }
      textWidthCacheCounter++;
      textWidthCache[key] = width;

      return width;
  }

  /**
   * @public
   * @param {string} text
   * @param {string} font
   * @param {string} [textAlign='left']
   * @param {string} [textVerticalAlign='top']
   * @param {Array.<number>} [textPadding]
   * @param {Object} [rich]
   * @param {Object} [truncate]
   * @return {Object} {x, y, width, height, lineHeight}
   */
  function getBoundingRect(text, font, textAlign, textVerticalAlign, textPadding, textLineHeight, rich, truncate) {
      return rich
          ? getRichTextRect(text, font, textAlign, textVerticalAlign, textPadding, textLineHeight, rich, truncate)
          : getPlainTextRect(text, font, textAlign, textVerticalAlign, textPadding, textLineHeight, truncate);
  }

  function getPlainTextRect(text, font, textAlign, textVerticalAlign, textPadding, textLineHeight, truncate) {
      var contentBlock = parsePlainText(text, font, textPadding, textLineHeight, truncate);
      var outerWidth = getWidth(text, font);
      if (textPadding) {
          outerWidth += textPadding[1] + textPadding[3];
      }
      var outerHeight = contentBlock.outerHeight;

      var x = adjustTextX(0, outerWidth, textAlign);
      var y = adjustTextY(0, outerHeight, textVerticalAlign);

      var rect = new BoundingRect(x, y, outerWidth, outerHeight);
      rect.lineHeight = contentBlock.lineHeight;

      return rect;
  }

  function getRichTextRect(text, font, textAlign, textVerticalAlign, textPadding, textLineHeight, rich, truncate) {
      var contentBlock = parseRichText(text, {
          rich: rich,
          truncate: truncate,
          font: font,
          textAlign: textAlign,
          textPadding: textPadding,
          textLineHeight: textLineHeight
      });
      var outerWidth = contentBlock.outerWidth;
      var outerHeight = contentBlock.outerHeight;

      var x = adjustTextX(0, outerWidth, textAlign);
      var y = adjustTextY(0, outerHeight, textVerticalAlign);

      return new BoundingRect(x, y, outerWidth, outerHeight);
  }

  /**
   * @public
   * @param {number} x
   * @param {number} width
   * @param {string} [textAlign='left']
   * @return {number} Adjusted x.
   */
  function adjustTextX(x, width, textAlign) {
      // FIXME Right to left language
      if (textAlign === 'right') {
          x -= width;
      }
      else if (textAlign === 'center') {
          x -= width / 2;
      }
      return x;
  }

  /**
   * @public
   * @param {number} y
   * @param {number} height
   * @param {string} [textVerticalAlign='top']
   * @return {number} Adjusted y.
   */
  function adjustTextY(y, height, textVerticalAlign) {
      if (textVerticalAlign === 'middle') {
          y -= height / 2;
      }
      else if (textVerticalAlign === 'bottom') {
          y -= height;
      }
      return y;
  }

  /**
   * @public
   * @param {stirng} textPosition
   * @param {Object} rect {x, y, width, height}
   * @param {number} distance
   * @return {Object} {x, y, textAlign, textVerticalAlign}
   */
  function adjustTextPositionOnRect(textPosition, rect, distance) {

      var x = rect.x;
      var y = rect.y;

      var height = rect.height;
      var width = rect.width;
      var halfHeight = height / 2;

      var textAlign = 'left';
      var textVerticalAlign = 'top';

      switch (textPosition) {
          case 'left':
              x -= distance;
              y += halfHeight;
              textAlign = 'right';
              textVerticalAlign = 'middle';
              break;
          case 'right':
              x += distance + width;
              y += halfHeight;
              textVerticalAlign = 'middle';
              break;
          case 'top':
              x += width / 2;
              y -= distance;
              textAlign = 'center';
              textVerticalAlign = 'bottom';
              break;
          case 'bottom':
              x += width / 2;
              y += height + distance;
              textAlign = 'center';
              break;
          case 'inside':
              x += width / 2;
              y += halfHeight;
              textAlign = 'center';
              textVerticalAlign = 'middle';
              break;
          case 'insideLeft':
              x += distance;
              y += halfHeight;
              textVerticalAlign = 'middle';
              break;
          case 'insideRight':
              x += width - distance;
              y += halfHeight;
              textAlign = 'right';
              textVerticalAlign = 'middle';
              break;
          case 'insideTop':
              x += width / 2;
              y += distance;
              textAlign = 'center';
              break;
          case 'insideBottom':
              x += width / 2;
              y += height - distance;
              textAlign = 'center';
              textVerticalAlign = 'bottom';
              break;
          case 'insideTopLeft':
              x += distance;
              y += distance;
              break;
          case 'insideTopRight':
              x += width - distance;
              y += distance;
              textAlign = 'right';
              break;
          case 'insideBottomLeft':
              x += distance;
              y += height - distance;
              textVerticalAlign = 'bottom';
              break;
          case 'insideBottomRight':
              x += width - distance;
              y += height - distance;
              textAlign = 'right';
              textVerticalAlign = 'bottom';
              break;
      }

      return {
          x: x,
          y: y,
          textAlign: textAlign,
          textVerticalAlign: textVerticalAlign
      };
  }

  /**
   * Show ellipsis if overflow.
   *
   * @public
   * @param  {string} text
   * @param  {string} containerWidth
   * @param  {string} font
   * @param  {number} [ellipsis='...']
   * @param  {Object} [options]
   * @param  {number} [options.maxIterations=3]
   * @param  {number} [options.minChar=0] If truncate result are less
   *                  then minChar, ellipsis will not show, which is
   *                  better for user hint in some cases.
   * @param  {number} [options.placeholder=''] When all truncated, use the placeholder.
   * @return {string}
   */
  function truncateText(text, containerWidth, font, ellipsis, options) {
      if (!containerWidth) {
          return '';
      }

      var textLines = (text + '').split('\n');
      options = prepareTruncateOptions(containerWidth, font, ellipsis, options);

      // FIXME
      // It is not appropriate that every line has '...' when truncate multiple lines.
      for (var i = 0, len = textLines.length; i < len; i++) {
          textLines[i] = truncateSingleLine(textLines[i], options);
      }

      return textLines.join('\n');
  }

  function prepareTruncateOptions(containerWidth, font, ellipsis, options) {
      options = extend({}, options);

      options.font = font;
      var ellipsis = retrieve2(ellipsis, '...');
      options.maxIterations = retrieve2(options.maxIterations, 2);
      var minChar = options.minChar = retrieve2(options.minChar, 0);
      // FIXME
      // Other languages?
      options.cnCharWidth = getWidth('国', font);
      // FIXME
      // Consider proportional font?
      var ascCharWidth = options.ascCharWidth = getWidth('a', font);
      options.placeholder = retrieve2(options.placeholder, '');

      // Example 1: minChar: 3, text: 'asdfzxcv', truncate result: 'asdf', but not: 'a...'.
      // Example 2: minChar: 3, text: '维度', truncate result: '维', but not: '...'.
      var contentWidth = containerWidth = Math.max(0, containerWidth - 1); // Reserve some gap.
      for (var i = 0; i < minChar && contentWidth >= ascCharWidth; i++) {
          contentWidth -= ascCharWidth;
      }

      var ellipsisWidth = getWidth(ellipsis, font);
      if (ellipsisWidth > contentWidth) {
          ellipsis = '';
          ellipsisWidth = 0;
      }

      contentWidth = containerWidth - ellipsisWidth;

      options.ellipsis = ellipsis;
      options.ellipsisWidth = ellipsisWidth;
      options.contentWidth = contentWidth;
      options.containerWidth = containerWidth;

      return options;
  }

  function truncateSingleLine(textLine, options) {
      var containerWidth = options.containerWidth;
      var font = options.font;
      var contentWidth = options.contentWidth;

      if (!containerWidth) {
          return '';
      }

      var lineWidth = getWidth(textLine, font);

      if (lineWidth <= containerWidth) {
          return textLine;
      }

      for (var j = 0; ; j++) {
          if (lineWidth <= contentWidth || j >= options.maxIterations) {
              textLine += options.ellipsis;
              break;
          }

          var subLength = j === 0
              ? estimateLength(textLine, contentWidth, options.ascCharWidth, options.cnCharWidth)
              : lineWidth > 0
              ? Math.floor(textLine.length * contentWidth / lineWidth)
              : 0;

          textLine = textLine.substr(0, subLength);
          lineWidth = getWidth(textLine, font);
      }

      if (textLine === '') {
          textLine = options.placeholder;
      }

      return textLine;
  }

  function estimateLength(text, contentWidth, ascCharWidth, cnCharWidth) {
      var width = 0;
      var i = 0;
      for (var len = text.length; i < len && width < contentWidth; i++) {
          var charCode = text.charCodeAt(i);
          width += (0 <= charCode && charCode <= 127) ? ascCharWidth : cnCharWidth;
      }
      return i;
  }

  /**
   * @public
   * @param {string} font
   * @return {number} line height
   */
  function getLineHeight(font) {
      // FIXME A rough approach.
      return getWidth('国', font);
  }

  /**
   * @public
   * @param {string} text
   * @param {string} font
   * @return {Object} width
   */
  function measureText(text, font) {
      return methods$1.measureText(text, font);
  }

  // Avoid assign to an exported variable, for transforming to cjs.
  methods$1.measureText = function (text, font) {
      var ctx = getContext();
      ctx.font = font || DEFAULT_FONT;
      return ctx.measureText(text);
  };

  /**
   * @public
   * @param {string} text
   * @param {string} font
   * @param {Object} [truncate]
   * @return {Object} block: {lineHeight, lines, height, outerHeight}
   *  Notice: for performance, do not calculate outerWidth util needed.
   */
  function parsePlainText(text, font, padding, textLineHeight, truncate) {
      text != null && (text += '');

      var lineHeight = retrieve2(textLineHeight, getLineHeight(font));
      var lines = text ? text.split('\n') : [];
      var height = lines.length * lineHeight;
      var outerHeight = height;

      if (padding) {
          outerHeight += padding[0] + padding[2];
      }

      if (text && truncate) {
          var truncOuterHeight = truncate.outerHeight;
          var truncOuterWidth = truncate.outerWidth;
          if (truncOuterHeight != null && outerHeight > truncOuterHeight) {
              text = '';
              lines = [];
          }
          else if (truncOuterWidth != null) {
              var options = prepareTruncateOptions(
                  truncOuterWidth - (padding ? padding[1] + padding[3] : 0),
                  font,
                  truncate.ellipsis,
                  {minChar: truncate.minChar, placeholder: truncate.placeholder}
              );

              // FIXME
              // It is not appropriate that every line has '...' when truncate multiple lines.
              for (var i = 0, len = lines.length; i < len; i++) {
                  lines[i] = truncateSingleLine(lines[i], options);
              }
          }
      }

      return {
          lines: lines,
          height: height,
          outerHeight: outerHeight,
          lineHeight: lineHeight
      };
  }

  /**
   * For example: 'some text {a|some text}other text{b|some text}xxx{c|}xxx'
   * Also consider 'bbbb{a|xxx\nzzz}xxxx\naaaa'.
   *
   * @public
   * @param {string} text
   * @param {Object} style
   * @return {Object} block
   * {
   *      width,
   *      height,
   *      lines: [{
   *          lineHeight,
   *          width,
   *          tokens: [[{
   *              styleName,
   *              text,
   *              width,      // include textPadding
   *              height,     // include textPadding
   *              textWidth, // pure text width
   *              textHeight, // pure text height
   *              lineHeihgt,
   *              font,
   *              textAlign,
   *              textVerticalAlign
   *          }], [...], ...]
   *      }, ...]
   * }
   * If styleName is undefined, it is plain text.
   */
  function parseRichText(text, style) {
      var contentBlock = {lines: [], width: 0, height: 0};

      text != null && (text += '');
      if (!text) {
          return contentBlock;
      }

      var lastIndex = STYLE_REG.lastIndex = 0;
      var result;
      while ((result = STYLE_REG.exec(text)) != null) {
          var matchedIndex = result.index;
          if (matchedIndex > lastIndex) {
              pushTokens(contentBlock, text.substring(lastIndex, matchedIndex));
          }
          pushTokens(contentBlock, result[2], result[1]);
          lastIndex = STYLE_REG.lastIndex;
      }

      if (lastIndex < text.length) {
          pushTokens(contentBlock, text.substring(lastIndex, text.length));
      }

      var lines = contentBlock.lines;
      var contentHeight = 0;
      var contentWidth = 0;
      // For `textWidth: 100%`
      var pendingList = [];

      var stlPadding = style.textPadding;

      var truncate = style.truncate;
      var truncateWidth = truncate && truncate.outerWidth;
      var truncateHeight = truncate && truncate.outerHeight;
      if (stlPadding) {
          truncateWidth != null && (truncateWidth -= stlPadding[1] + stlPadding[3]);
          truncateHeight != null && (truncateHeight -= stlPadding[0] + stlPadding[2]);
      }

      // Calculate layout info of tokens.
      for (var i = 0; i < lines.length; i++) {
          var line = lines[i];
          var lineHeight = 0;
          var lineWidth = 0;

          for (var j = 0; j < line.tokens.length; j++) {
              var token = line.tokens[j];
              var tokenStyle = token.styleName && style.rich[token.styleName] || {};
              // textPadding should not inherit from style.
              var textPadding = token.textPadding = tokenStyle.textPadding;

              // textFont has been asigned to font by `normalizeStyle`.
              var font = token.font = tokenStyle.font || style.font;

              // textHeight can be used when textVerticalAlign is specified in token.
              var tokenHeight = token.textHeight = retrieve2(
                  // textHeight should not be inherited, consider it can be specified
                  // as box height of the block.
                  tokenStyle.textHeight, getLineHeight(font)
              );
              textPadding && (tokenHeight += textPadding[0] + textPadding[2]);
              token.height = tokenHeight;
              token.lineHeight = retrieve3(
                  tokenStyle.textLineHeight, style.textLineHeight, tokenHeight
              );

              token.textAlign = tokenStyle && tokenStyle.textAlign || style.textAlign;
              token.textVerticalAlign = tokenStyle && tokenStyle.textVerticalAlign || 'middle';

              if (truncateHeight != null && contentHeight + token.lineHeight > truncateHeight) {
                  return {lines: [], width: 0, height: 0};
              }

              token.textWidth = getWidth(token.text, font);
              var tokenWidth = tokenStyle.textWidth;
              var tokenWidthNotSpecified = tokenWidth == null || tokenWidth === 'auto';

              // Percent width, can be `100%`, can be used in drawing separate
              // line when box width is needed to be auto.
              if (typeof tokenWidth === 'string' && tokenWidth.charAt(tokenWidth.length - 1) === '%') {
                  token.percentWidth = tokenWidth;
                  pendingList.push(token);
                  tokenWidth = 0;
                  // Do not truncate in this case, because there is no user case
                  // and it is too complicated.
              }
              else {
                  if (tokenWidthNotSpecified) {
                      tokenWidth = token.textWidth;

                      // FIXME: If image is not loaded and textWidth is not specified, calling
                      // `getBoundingRect()` will not get correct result.
                      var textBackgroundColor = tokenStyle.textBackgroundColor;
                      var bgImg = textBackgroundColor && textBackgroundColor.image;

                      // Use cases:
                      // (1) If image is not loaded, it will be loaded at render phase and call
                      // `dirty()` and `textBackgroundColor.image` will be replaced with the loaded
                      // image, and then the right size will be calculated here at the next tick.
                      // See `graphic/helper/text.js`.
                      // (2) If image loaded, and `textBackgroundColor.image` is image src string,
                      // use `imageHelper.findExistImage` to find cached image.
                      // `imageHelper.findExistImage` will always be called here before
                      // `imageHelper.createOrUpdateImage` in `graphic/helper/text.js#renderRichText`
                      // which ensures that image will not be rendered before correct size calcualted.
                      if (bgImg) {
                          bgImg = findExistImage(bgImg);
                          if (isImageReady(bgImg)) {
                              tokenWidth = Math.max(tokenWidth, bgImg.width * tokenHeight / bgImg.height);
                          }
                      }
                  }

                  var paddingW = textPadding ? textPadding[1] + textPadding[3] : 0;
                  tokenWidth += paddingW;

                  var remianTruncWidth = truncateWidth != null ? truncateWidth - lineWidth : null;

                  if (remianTruncWidth != null && remianTruncWidth < tokenWidth) {
                      if (!tokenWidthNotSpecified || remianTruncWidth < paddingW) {
                          token.text = '';
                          token.textWidth = tokenWidth = 0;
                      }
                      else {
                          token.text = truncateText(
                              token.text, remianTruncWidth - paddingW, font, truncate.ellipsis,
                              {minChar: truncate.minChar}
                          );
                          token.textWidth = getWidth(token.text, font);
                          tokenWidth = token.textWidth + paddingW;
                      }
                  }
              }

              lineWidth += (token.width = tokenWidth);
              tokenStyle && (lineHeight = Math.max(lineHeight, token.lineHeight));
          }

          line.width = lineWidth;
          line.lineHeight = lineHeight;
          contentHeight += lineHeight;
          contentWidth = Math.max(contentWidth, lineWidth);
      }

      contentBlock.outerWidth = contentBlock.width = retrieve2(style.textWidth, contentWidth);
      contentBlock.outerHeight = contentBlock.height = retrieve2(style.textHeight, contentHeight);

      if (stlPadding) {
          contentBlock.outerWidth += stlPadding[1] + stlPadding[3];
          contentBlock.outerHeight += stlPadding[0] + stlPadding[2];
      }

      for (var i = 0; i < pendingList.length; i++) {
          var token = pendingList[i];
          var percentWidth = token.percentWidth;
          // Should not base on outerWidth, because token can not be placed out of padding.
          token.width = parseInt(percentWidth, 10) / 100 * contentWidth;
      }

      return contentBlock;
  }

  function pushTokens(block, str, styleName) {
      var isEmptyStr = str === '';
      var strs = str.split('\n');
      var lines = block.lines;

      for (var i = 0; i < strs.length; i++) {
          var text = strs[i];
          var token = {
              styleName: styleName,
              text: text,
              isLineHolder: !text && !isEmptyStr
          };

          // The first token should be appended to the last line.
          if (!i) {
              var tokens = (lines[lines.length - 1] || (lines[0] = {tokens: []})).tokens;

              // Consider cases:
              // (1) ''.split('\n') => ['', '\n', ''], the '' at the first item
              // (which is a placeholder) should be replaced by new token.
              // (2) A image backage, where token likes {a|}.
              // (3) A redundant '' will affect textAlign in line.
              // (4) tokens with the same tplName should not be merged, because
              // they should be displayed in different box (with border and padding).
              var tokensLen = tokens.length;
              (tokensLen === 1 && tokens[0].isLineHolder)
                  ? (tokens[0] = token)
                  // Consider text is '', only insert when it is the "lineHolder" or
                  // "emptyStr". Otherwise a redundant '' will affect textAlign in line.
                  : ((text || !tokensLen || isEmptyStr) && tokens.push(token));
          }
          // Other tokens always start a new line.
          else {
              // If there is '', insert it as a placeholder.
              lines.push({tokens: [token]});
          }
      }
  }

  function makeFont(style) {
      // FIXME in node-canvas fontWeight is before fontStyle
      // Use `fontSize` `fontFamily` to check whether font properties are defined.
      var font = (style.fontSize || style.fontFamily) && [
          style.fontStyle,
          style.fontWeight,
          (style.fontSize || 12) + 'px',
          // If font properties are defined, `fontFamily` should not be ignored.
          style.fontFamily || 'sans-serif'
      ].join(' ');
      return font && trim(font) || style.textFont || style.font;
  }

  /**
   * @param {Object} ctx
   * @param {Object} shape
   * @param {number} shape.x
   * @param {number} shape.y
   * @param {number} shape.width
   * @param {number} shape.height
   * @param {number} shape.r
   */
  function buildPath(ctx, shape) {
      var x = shape.x;
      var y = shape.y;
      var width = shape.width;
      var height = shape.height;
      var r = shape.r;
      var r1;
      var r2;
      var r3;
      var r4;

      // Convert width and height to positive for better borderRadius
      if (width < 0) {
          x = x + width;
          width = -width;
      }
      if (height < 0) {
          y = y + height;
          height = -height;
      }

      if (typeof r === 'number') {
          r1 = r2 = r3 = r4 = r;
      }
      else if (r instanceof Array) {
          if (r.length === 1) {
              r1 = r2 = r3 = r4 = r[0];
          }
          else if (r.length === 2) {
              r1 = r3 = r[0];
              r2 = r4 = r[1];
          }
          else if (r.length === 3) {
              r1 = r[0];
              r2 = r4 = r[1];
              r3 = r[2];
          }
          else {
              r1 = r[0];
              r2 = r[1];
              r3 = r[2];
              r4 = r[3];
          }
      }
      else {
          r1 = r2 = r3 = r4 = 0;
      }

      var total;
      if (r1 + r2 > width) {
          total = r1 + r2;
          r1 *= width / total;
          r2 *= width / total;
      }
      if (r3 + r4 > width) {
          total = r3 + r4;
          r3 *= width / total;
          r4 *= width / total;
      }
      if (r2 + r3 > height) {
          total = r2 + r3;
          r2 *= height / total;
          r3 *= height / total;
      }
      if (r1 + r4 > height) {
          total = r1 + r4;
          r1 *= height / total;
          r4 *= height / total;
      }
      ctx.moveTo(x + r1, y);
      ctx.lineTo(x + width - r2, y);
      r2 !== 0 && ctx.arc(x + width - r2, y + r2, r2, -Math.PI / 2, 0);
      ctx.lineTo(x + width, y + height - r3);
      r3 !== 0 && ctx.arc(x + width - r3, y + height - r3, r3, 0, Math.PI / 2);
      ctx.lineTo(x + r4, y + height);
      r4 !== 0 && ctx.arc(x + r4, y + height - r4, r4, Math.PI / 2, Math.PI);
      ctx.lineTo(x, y + r1);
      r1 !== 0 && ctx.arc(x + r1, y + r1, r1, Math.PI, Math.PI * 1.5);
  }

  var DEFAULT_FONT$1 = DEFAULT_FONT;

  // TODO: Have not support 'start', 'end' yet.
  var VALID_TEXT_ALIGN = {left: 1, right: 1, center: 1};
  var VALID_TEXT_VERTICAL_ALIGN = {top: 1, bottom: 1, middle: 1};
  // Different from `STYLE_COMMON_PROPS` of `graphic/Style`,
  // the default value of shadowColor is `'transparent'`.
  var SHADOW_STYLE_COMMON_PROPS = [
      ['textShadowBlur', 'shadowBlur', 0],
      ['textShadowOffsetX', 'shadowOffsetX', 0],
      ['textShadowOffsetY', 'shadowOffsetY', 0],
      ['textShadowColor', 'shadowColor', 'transparent']
  ];

  /**
   * @param {module:zrender/graphic/Style} style
   * @return {module:zrender/graphic/Style} The input style.
   */
  function normalizeTextStyle(style) {
      normalizeStyle(style);
      each(style.rich, normalizeStyle);
      return style;
  }

  function normalizeStyle(style) {
      if (style) {

          style.font = makeFont(style);

          var textAlign = style.textAlign;
          textAlign === 'middle' && (textAlign = 'center');
          style.textAlign = (
              textAlign == null || VALID_TEXT_ALIGN[textAlign]
          ) ? textAlign : 'left';

          // Compatible with textBaseline.
          var textVerticalAlign = style.textVerticalAlign || style.textBaseline;
          textVerticalAlign === 'center' && (textVerticalAlign = 'middle');
          style.textVerticalAlign = (
              textVerticalAlign == null || VALID_TEXT_VERTICAL_ALIGN[textVerticalAlign]
          ) ? textVerticalAlign : 'top';

          var textPadding = style.textPadding;
          if (textPadding) {
              style.textPadding = normalizeCssArray(style.textPadding);
          }
      }
  }

  /**
   * @param {CanvasRenderingContext2D} ctx
   * @param {string} text
   * @param {module:zrender/graphic/Style} style
   * @param {Object|boolean} [rect] {x, y, width, height}
   *                  If set false, rect text is not used.
   * @param {Element|module:zrender/graphic/helper/constant.WILL_BE_RESTORED} [prevEl] For ctx prop cache.
   */
  function renderText(hostEl, ctx, text, style, rect, prevEl) {
      style.rich
          ? renderRichText(hostEl, ctx, text, style, rect, prevEl)
          : renderPlainText(hostEl, ctx, text, style, rect, prevEl);
  }

  // Avoid setting to ctx according to prevEl if possible for
  // performance in scenarios of large amount text.
  function renderPlainText(hostEl, ctx, text, style, rect, prevEl) {

      var needDrawBg = needDrawBackground(style);

      var prevStyle;
      var checkCache = false;
      var cachedByMe = ctx.__attrCachedBy === ContextCachedBy.PLAIN_TEXT;

      // Only take and check cache for `Text` el, but not RectText.
      if (prevEl !== WILL_BE_RESTORED) {
          if (prevEl) {
              prevStyle = prevEl.style;
              checkCache = !needDrawBg && cachedByMe && prevStyle;
          }

          // Prevent from using cache in `Style::bind`, because of the case:
          // ctx property is modified by other properties than `Style::bind`
          // used, and Style::bind is called next.
          ctx.__attrCachedBy = needDrawBg ? ContextCachedBy.NONE : ContextCachedBy.PLAIN_TEXT;
      }
      // Since this will be restored, prevent from using these props to check cache in the next
      // entering of this method. But do not need to clear other cache like `Style::bind`.
      else if (cachedByMe) {
          ctx.__attrCachedBy = ContextCachedBy.NONE;
      }

      var styleFont = style.font || DEFAULT_FONT$1;
      // PENDING
      // Only `Text` el set `font` and keep it (`RectText` will restore). So theoretically
      // we can make font cache on ctx, which can cache for text el that are discontinuous.
      // But layer save/restore needed to be considered.
      // if (styleFont !== ctx.__fontCache) {
      //     ctx.font = styleFont;
      //     if (prevEl !== WILL_BE_RESTORED) {
      //         ctx.__fontCache = styleFont;
      //     }
      // }
      if (!checkCache || styleFont !== (prevStyle.font || DEFAULT_FONT$1)) {
          ctx.font = styleFont;
      }

      // Use the final font from context-2d, because the final
      // font might not be the style.font when it is illegal.
      // But get `ctx.font` might be time consuming.
      var computedFont = hostEl.__computedFont;
      if (hostEl.__styleFont !== styleFont) {
          hostEl.__styleFont = styleFont;
          computedFont = hostEl.__computedFont = ctx.font;
      }

      var textPadding = style.textPadding;
      var textLineHeight = style.textLineHeight;

      var contentBlock = hostEl.__textCotentBlock;
      if (!contentBlock || hostEl.__dirtyText) {
          contentBlock = hostEl.__textCotentBlock = parsePlainText(
              text, computedFont, textPadding, textLineHeight, style.truncate
          );
      }

      var outerHeight = contentBlock.outerHeight;

      var textLines = contentBlock.lines;
      var lineHeight = contentBlock.lineHeight;

      var boxPos = getBoxPosition(outerHeight, style, rect);
      var baseX = boxPos.baseX;
      var baseY = boxPos.baseY;
      var textAlign = boxPos.textAlign || 'left';
      var textVerticalAlign = boxPos.textVerticalAlign;

      // Origin of textRotation should be the base point of text drawing.
      applyTextRotation(ctx, style, rect, baseX, baseY);

      var boxY = adjustTextY(baseY, outerHeight, textVerticalAlign);
      var textX = baseX;
      var textY = boxY;

      if (needDrawBg || textPadding) {
          // Consider performance, do not call getTextWidth util necessary.
          var textWidth = getWidth(text, computedFont);
          var outerWidth = textWidth;
          textPadding && (outerWidth += textPadding[1] + textPadding[3]);
          var boxX = adjustTextX(baseX, outerWidth, textAlign);

          needDrawBg && drawBackground(hostEl, ctx, style, boxX, boxY, outerWidth, outerHeight);

          if (textPadding) {
              textX = getTextXForPadding(baseX, textAlign, textPadding);
              textY += textPadding[0];
          }
      }

      // Always set textAlign and textBase line, because it is difficute to calculate
      // textAlign from prevEl, and we dont sure whether textAlign will be reset if
      // font set happened.
      ctx.textAlign = textAlign;
      // Force baseline to be "middle". Otherwise, if using "top", the
      // text will offset downward a little bit in font "Microsoft YaHei".
      ctx.textBaseline = 'middle';
      // Set text opacity
      ctx.globalAlpha = style.opacity || 1;

      // Always set shadowBlur and shadowOffset to avoid leak from displayable.
      for (var i = 0; i < SHADOW_STYLE_COMMON_PROPS.length; i++) {
          var propItem = SHADOW_STYLE_COMMON_PROPS[i];
          var styleProp = propItem[0];
          var ctxProp = propItem[1];
          var val = style[styleProp];
          if (!checkCache || val !== prevStyle[styleProp]) {
              ctx[ctxProp] = fixShadow(ctx, ctxProp, val || propItem[2]);
          }
      }

      // `textBaseline` is set as 'middle'.
      textY += lineHeight / 2;

      var textStrokeWidth = style.textStrokeWidth;
      var textStrokeWidthPrev = checkCache ? prevStyle.textStrokeWidth : null;
      var strokeWidthChanged = !checkCache || textStrokeWidth !== textStrokeWidthPrev;
      var strokeChanged = !checkCache || strokeWidthChanged || style.textStroke !== prevStyle.textStroke;
      var textStroke = getStroke(style.textStroke, textStrokeWidth);
      var textFill = getFill(style.textFill);

      if (textStroke) {
          if (strokeWidthChanged) {
              ctx.lineWidth = textStrokeWidth;
          }
          if (strokeChanged) {
              ctx.strokeStyle = textStroke;
          }
      }
      if (textFill) {
          if (!checkCache || style.textFill !== prevStyle.textFill) {
              ctx.fillStyle = textFill;
          }
      }

      // Optimize simply, in most cases only one line exists.
      if (textLines.length === 1) {
          // Fill after stroke so the outline will not cover the main part.
          textStroke && ctx.strokeText(textLines[0], textX, textY);
          textFill && ctx.fillText(textLines[0], textX, textY);
      }
      else {
          for (var i = 0; i < textLines.length; i++) {
              // Fill after stroke so the outline will not cover the main part.
              textStroke && ctx.strokeText(textLines[i], textX, textY);
              textFill && ctx.fillText(textLines[i], textX, textY);
              textY += lineHeight;
          }
      }
  }

  function renderRichText(hostEl, ctx, text, style, rect, prevEl) {
      // Do not do cache for rich text because of the complexity.
      // But `RectText` this will be restored, do not need to clear other cache like `Style::bind`.
      if (prevEl !== WILL_BE_RESTORED) {
          ctx.__attrCachedBy = ContextCachedBy.NONE;
      }

      var contentBlock = hostEl.__textCotentBlock;

      if (!contentBlock || hostEl.__dirtyText) {
          contentBlock = hostEl.__textCotentBlock = parseRichText(text, style);
      }

      drawRichText(hostEl, ctx, contentBlock, style, rect);
  }

  function drawRichText(hostEl, ctx, contentBlock, style, rect) {
      var contentWidth = contentBlock.width;
      var outerWidth = contentBlock.outerWidth;
      var outerHeight = contentBlock.outerHeight;
      var textPadding = style.textPadding;

      var boxPos = getBoxPosition(outerHeight, style, rect);
      var baseX = boxPos.baseX;
      var baseY = boxPos.baseY;
      var textAlign = boxPos.textAlign;
      var textVerticalAlign = boxPos.textVerticalAlign;

      // Origin of textRotation should be the base point of text drawing.
      applyTextRotation(ctx, style, rect, baseX, baseY);

      var boxX = adjustTextX(baseX, outerWidth, textAlign);
      var boxY = adjustTextY(baseY, outerHeight, textVerticalAlign);
      var xLeft = boxX;
      var lineTop = boxY;
      if (textPadding) {
          xLeft += textPadding[3];
          lineTop += textPadding[0];
      }
      var xRight = xLeft + contentWidth;

      needDrawBackground(style) && drawBackground(
          hostEl, ctx, style, boxX, boxY, outerWidth, outerHeight
      );

      for (var i = 0; i < contentBlock.lines.length; i++) {
          var line = contentBlock.lines[i];
          var tokens = line.tokens;
          var tokenCount = tokens.length;
          var lineHeight = line.lineHeight;
          var usedWidth = line.width;

          var leftIndex = 0;
          var lineXLeft = xLeft;
          var lineXRight = xRight;
          var rightIndex = tokenCount - 1;
          var token;

          while (
              leftIndex < tokenCount
              && (token = tokens[leftIndex], !token.textAlign || token.textAlign === 'left')
          ) {
              placeToken(hostEl, ctx, token, style, lineHeight, lineTop, lineXLeft, 'left');
              usedWidth -= token.width;
              lineXLeft += token.width;
              leftIndex++;
          }

          while (
              rightIndex >= 0
              && (token = tokens[rightIndex], token.textAlign === 'right')
          ) {
              placeToken(hostEl, ctx, token, style, lineHeight, lineTop, lineXRight, 'right');
              usedWidth -= token.width;
              lineXRight -= token.width;
              rightIndex--;
          }

          // The other tokens are placed as textAlign 'center' if there is enough space.
          lineXLeft += (contentWidth - (lineXLeft - xLeft) - (xRight - lineXRight) - usedWidth) / 2;
          while (leftIndex <= rightIndex) {
              token = tokens[leftIndex];
              // Consider width specified by user, use 'center' rather than 'left'.
              placeToken(hostEl, ctx, token, style, lineHeight, lineTop, lineXLeft + token.width / 2, 'center');
              lineXLeft += token.width;
              leftIndex++;
          }

          lineTop += lineHeight;
      }
  }

  function applyTextRotation(ctx, style, rect, x, y) {
      // textRotation only apply in RectText.
      if (rect && style.textRotation) {
          var origin = style.textOrigin;
          if (origin === 'center') {
              x = rect.width / 2 + rect.x;
              y = rect.height / 2 + rect.y;
          }
          else if (origin) {
              x = origin[0] + rect.x;
              y = origin[1] + rect.y;
          }

          ctx.translate(x, y);
          // Positive: anticlockwise
          ctx.rotate(-style.textRotation);
          ctx.translate(-x, -y);
      }
  }

  function placeToken(hostEl, ctx, token, style, lineHeight, lineTop, x, textAlign) {
      var tokenStyle = style.rich[token.styleName] || {};
      tokenStyle.text = token.text;

      // 'ctx.textBaseline' is always set as 'middle', for sake of
      // the bias of "Microsoft YaHei".
      var textVerticalAlign = token.textVerticalAlign;
      var y = lineTop + lineHeight / 2;
      if (textVerticalAlign === 'top') {
          y = lineTop + token.height / 2;
      }
      else if (textVerticalAlign === 'bottom') {
          y = lineTop + lineHeight - token.height / 2;
      }

      !token.isLineHolder && needDrawBackground(tokenStyle) && drawBackground(
          hostEl,
          ctx,
          tokenStyle,
          textAlign === 'right'
              ? x - token.width
              : textAlign === 'center'
              ? x - token.width / 2
              : x,
          y - token.height / 2,
          token.width,
          token.height
      );

      var textPadding = token.textPadding;
      if (textPadding) {
          x = getTextXForPadding(x, textAlign, textPadding);
          y -= token.height / 2 - textPadding[2] - token.textHeight / 2;
      }

      setCtx(ctx, 'shadowBlur', retrieve3(tokenStyle.textShadowBlur, style.textShadowBlur, 0));
      setCtx(ctx, 'shadowColor', tokenStyle.textShadowColor || style.textShadowColor || 'transparent');
      setCtx(ctx, 'shadowOffsetX', retrieve3(tokenStyle.textShadowOffsetX, style.textShadowOffsetX, 0));
      setCtx(ctx, 'shadowOffsetY', retrieve3(tokenStyle.textShadowOffsetY, style.textShadowOffsetY, 0));

      setCtx(ctx, 'textAlign', textAlign);
      // Force baseline to be "middle". Otherwise, if using "top", the
      // text will offset downward a little bit in font "Microsoft YaHei".
      setCtx(ctx, 'textBaseline', 'middle');

      setCtx(ctx, 'font', token.font || DEFAULT_FONT$1);

      var textStroke = getStroke(tokenStyle.textStroke || style.textStroke, textStrokeWidth);
      var textFill = getFill(tokenStyle.textFill || style.textFill);
      var textStrokeWidth = retrieve2(tokenStyle.textStrokeWidth, style.textStrokeWidth);

      // Fill after stroke so the outline will not cover the main part.
      if (textStroke) {
          setCtx(ctx, 'lineWidth', textStrokeWidth);
          setCtx(ctx, 'strokeStyle', textStroke);
          ctx.strokeText(token.text, x, y);
      }
      if (textFill) {
          setCtx(ctx, 'fillStyle', textFill);
          ctx.fillText(token.text, x, y);
      }
  }

  function needDrawBackground(style) {
      return !!(
          style.textBackgroundColor
          || (style.textBorderWidth && style.textBorderColor)
      );
  }

  // style: {textBackgroundColor, textBorderWidth, textBorderColor, textBorderRadius, text}
  // shape: {x, y, width, height}
  function drawBackground(hostEl, ctx, style, x, y, width, height) {
      var textBackgroundColor = style.textBackgroundColor;
      var textBorderWidth = style.textBorderWidth;
      var textBorderColor = style.textBorderColor;
      var isPlainBg = isString(textBackgroundColor);

      setCtx(ctx, 'shadowBlur', style.textBoxShadowBlur || 0);
      setCtx(ctx, 'shadowColor', style.textBoxShadowColor || 'transparent');
      setCtx(ctx, 'shadowOffsetX', style.textBoxShadowOffsetX || 0);
      setCtx(ctx, 'shadowOffsetY', style.textBoxShadowOffsetY || 0);

      if (isPlainBg || (textBorderWidth && textBorderColor)) {
          ctx.beginPath();
          var textBorderRadius = style.textBorderRadius;
          if (!textBorderRadius) {
              ctx.rect(x, y, width, height);
          }
          else {
              buildPath(ctx, {
                  x: x, y: y, width: width, height: height, r: textBorderRadius
              });
          }
          ctx.closePath();
      }

      if (isPlainBg) {
          setCtx(ctx, 'fillStyle', textBackgroundColor);

          if (style.fillOpacity != null) {
              var originalGlobalAlpha = ctx.globalAlpha;
              ctx.globalAlpha = style.fillOpacity * style.opacity;
              ctx.fill();
              ctx.globalAlpha = originalGlobalAlpha;
          }
          else {
              ctx.fill();
          }
      }
      else if (isObject(textBackgroundColor)) {
          var image = textBackgroundColor.image;

          image = createOrUpdateImage(
              image, null, hostEl, onBgImageLoaded, textBackgroundColor
          );
          if (image && isImageReady(image)) {
              ctx.drawImage(image, x, y, width, height);
          }
      }

      if (textBorderWidth && textBorderColor) {
          setCtx(ctx, 'lineWidth', textBorderWidth);
          setCtx(ctx, 'strokeStyle', textBorderColor);

          if (style.strokeOpacity != null) {
              var originalGlobalAlpha = ctx.globalAlpha;
              ctx.globalAlpha = style.strokeOpacity * style.opacity;
              ctx.stroke();
              ctx.globalAlpha = originalGlobalAlpha;
          }
          else {
              ctx.stroke();
          }
      }
  }

  function onBgImageLoaded(image, textBackgroundColor) {
      // Replace image, so that `contain/text.js#parseRichText`
      // will get correct result in next tick.
      textBackgroundColor.image = image;
  }

  function getBoxPosition(blockHeiht, style, rect) {
      var baseX = style.x || 0;
      var baseY = style.y || 0;
      var textAlign = style.textAlign;
      var textVerticalAlign = style.textVerticalAlign;

      // Text position represented by coord
      if (rect) {
          var textPosition = style.textPosition;
          if (textPosition instanceof Array) {
              // Percent
              baseX = rect.x + parsePercent(textPosition[0], rect.width);
              baseY = rect.y + parsePercent(textPosition[1], rect.height);
          }
          else {
              var res = adjustTextPositionOnRect(
                  textPosition, rect, style.textDistance
              );
              baseX = res.x;
              baseY = res.y;
              // Default align and baseline when has textPosition
              textAlign = textAlign || res.textAlign;
              textVerticalAlign = textVerticalAlign || res.textVerticalAlign;
          }

          // textOffset is only support in RectText, otherwise
          // we have to adjust boundingRect for textOffset.
          var textOffset = style.textOffset;
          if (textOffset) {
              baseX += textOffset[0];
              baseY += textOffset[1];
          }
      }

      return {
          baseX: baseX,
          baseY: baseY,
          textAlign: textAlign,
          textVerticalAlign: textVerticalAlign
      };
  }

  function setCtx(ctx, prop, value) {
      ctx[prop] = fixShadow(ctx, prop, value);
      return ctx[prop];
  }

  /**
   * @param {string} [stroke] If specified, do not check style.textStroke.
   * @param {string} [lineWidth] If specified, do not check style.textStroke.
   * @param {number} style
   */
  function getStroke(stroke, lineWidth) {
      return (stroke == null || lineWidth <= 0 || stroke === 'transparent' || stroke === 'none')
          ? null
          // TODO pattern and gradient?
          : (stroke.image || stroke.colorStops)
          ? '#000'
          : stroke;
  }

  function getFill(fill) {
      return (fill == null || fill === 'none')
          ? null
          // TODO pattern and gradient?
          : (fill.image || fill.colorStops)
          ? '#000'
          : fill;
  }

  function parsePercent(value, maxValue) {
      if (typeof value === 'string') {
          if (value.lastIndexOf('%') >= 0) {
              return parseFloat(value) / 100 * maxValue;
          }
          return parseFloat(value);
      }
      return value;
  }

  function getTextXForPadding(x, textAlign, textPadding) {
      return textAlign === 'right'
          ? (x - textPadding[1])
          : textAlign === 'center'
          ? (x + textPadding[3] / 2 - textPadding[1] / 2)
          : (x + textPadding[3]);
  }

  /**
   * @param {string} text
   * @param {module:zrender/Style} style
   * @return {boolean}
   */
  function needDrawText(text, style) {
      return text != null
          && (text
              || style.textBackgroundColor
              || (style.textBorderWidth && style.textBorderColor)
              || style.textPadding
          );
  }

  /**
   * Mixin for drawing text in a element bounding rect
   * @module zrender/mixin/RectText
   */

  var tmpRect = new BoundingRect();

  var RectText = function () {};

  RectText.prototype = {

      constructor: RectText,

      /**
       * Draw text in a rect with specified position.
       * @param  {CanvasRenderingContext2D} ctx
       * @param  {Object} rect Displayable rect
       */
      drawRectText: function (ctx, rect) {
          var style = this.style;

          rect = style.textRect || rect;

          // Optimize, avoid normalize every time.
          this.__dirty && normalizeTextStyle(style);

          var text = style.text;

          // Convert to string
          text != null && (text += '');

          if (!needDrawText(text, style)) {
              return;
          }

          // FIXME
          // Do not provide prevEl to `textHelper.renderText` for ctx prop cache,
          // but use `ctx.save()` and `ctx.restore()`. Because the cache for rect
          // text propably break the cache for its host elements.
          ctx.save();

          // Transform rect to view space
          var transform = this.transform;
          if (!style.transformText) {
              if (transform) {
                  tmpRect.copy(rect);
                  tmpRect.applyTransform(transform);
                  rect = tmpRect;
              }
          }
          else {
              this.setTransform(ctx);
          }

          // transformText and textRotation can not be used at the same time.
          renderText(this, ctx, text, style, rect, WILL_BE_RESTORED);

          ctx.restore();
      }
  };

  /**
   * 可绘制的图形基类
   * Base class of all displayable graphic objects
   * @module zrender/graphic/Displayable
   */

  /**
   * @alias module:zrender/graphic/Displayable
   * @extends module:zrender/Element
   * @extends module:zrender/graphic/mixin/RectText
   */
  function Displayable(opts) {

      opts = opts || {};

      Element.call(this, opts);

      // Extend properties
      for (var name in opts) {
          if (
              opts.hasOwnProperty(name)
                  && name !== 'style'
          ) {
              this[name] = opts[name];
          }
      }

      /**
       * @type {module:zrender/graphic/Style}
       */
      this.style = new Style(opts.style, this);

      this._rect = null;
      // Shapes for cascade clipping.
      this.__clipPaths = [];

      // FIXME Stateful must be mixined after style is setted
      // Stateful.call(this, opts);
  }

  Displayable.prototype = {

      constructor: Displayable,

      type: 'displayable',

      /**
       * Displayable 是否为脏，Painter 中会根据该标记判断是否需要是否需要重新绘制
       * Dirty flag. From which painter will determine if this displayable object needs brush
       * @name module:zrender/graphic/Displayable#__dirty
       * @type {boolean}
       */
      __dirty: true,

      /**
       * 图形是否可见，为true时不绘制图形，但是仍能触发鼠标事件
       * If ignore drawing of the displayable object. Mouse event will still be triggered
       * @name module:/zrender/graphic/Displayable#invisible
       * @type {boolean}
       * @default false
       */
      invisible: false,

      /**
       * @name module:/zrender/graphic/Displayable#z
       * @type {number}
       * @default 0
       */
      z: 0,

      /**
       * @name module:/zrender/graphic/Displayable#z
       * @type {number}
       * @default 0
       */
      z2: 0,

      /**
       * z层level，决定绘画在哪层canvas中
       * @name module:/zrender/graphic/Displayable#zlevel
       * @type {number}
       * @default 0
       */
      zlevel: 0,

      /**
       * 是否可拖拽
       * @name module:/zrender/graphic/Displayable#draggable
       * @type {boolean}
       * @default false
       */
      draggable: false,

      /**
       * 是否正在拖拽
       * @name module:/zrender/graphic/Displayable#draggable
       * @type {boolean}
       * @default false
       */
      dragging: false,

      /**
       * 是否相应鼠标事件
       * @name module:/zrender/graphic/Displayable#silent
       * @type {boolean}
       * @default false
       */
      silent: false,

      /**
       * If enable culling
       * @type {boolean}
       * @default false
       */
      culling: false,

      /**
       * Mouse cursor when hovered
       * @name module:/zrender/graphic/Displayable#cursor
       * @type {string}
       */
      cursor: 'pointer',

      /**
       * If hover area is bounding rect
       * @name module:/zrender/graphic/Displayable#rectHover
       * @type {string}
       */
      rectHover: false,

      /**
       * Render the element progressively when the value >= 0,
       * usefull for large data.
       * @type {boolean}
       */
      progressive: false,

      /**
       * @type {boolean}
       */
      incremental: false,
      /**
       * Scale ratio for global scale.
       * @type {boolean}
       */
      globalScaleRatio: 1,

      beforeBrush: function (ctx) {},

      afterBrush: function (ctx) {},

      /**
       * 图形绘制方法
       * @param {CanvasRenderingContext2D} ctx
       */
      // Interface
      brush: function (ctx, prevEl) {},

      /**
       * 获取最小包围盒
       * @return {module:zrender/core/BoundingRect}
       */
      // Interface
      getBoundingRect: function () {},

      /**
       * 判断坐标 x, y 是否在图形上
       * If displayable element contain coord x, y
       * @param  {number} x
       * @param  {number} y
       * @return {boolean}
       */
      contain: function (x, y) {
          return this.rectContain(x, y);
      },

      /**
       * @param  {Function} cb
       * @param  {}   context
       */
      traverse: function (cb, context) {
          cb.call(context, this);
      },

      /**
       * 判断坐标 x, y 是否在图形的包围盒上
       * If bounding rect of element contain coord x, y
       * @param  {number} x
       * @param  {number} y
       * @return {boolean}
       */
      rectContain: function (x, y) {
          var coord = this.transformCoordToLocal(x, y);
          var rect = this.getBoundingRect();
          return rect.contain(coord[0], coord[1]);
      },

      /**
       * 标记图形元素为脏，并且在下一帧重绘
       * Mark displayable element dirty and refresh next frame
       */
      dirty: function () {
          this.__dirty = this.__dirtyText = true;

          this._rect = null;

          this.__zr && this.__zr.refresh();
      },

      /**
       * 图形是否会触发事件
       * If displayable object binded any event
       * @return {boolean}
       */
      // TODO, 通过 bind 绑定的事件
      // isSilent: function () {
      //     return !(
      //         this.hoverable || this.draggable
      //         || this.onmousemove || this.onmouseover || this.onmouseout
      //         || this.onmousedown || this.onmouseup || this.onclick
      //         || this.ondragenter || this.ondragover || this.ondragleave
      //         || this.ondrop
      //     );
      // },
      /**
       * Alias for animate('style')
       * @param {boolean} loop
       */
      animateStyle: function (loop) {
          return this.animate('style', loop);
      },

      attrKV: function (key, value) {
          if (key !== 'style') {
              Element.prototype.attrKV.call(this, key, value);
          }
          else {
              this.style.set(value);
          }
      },

      /**
       * @param {Object|string} key
       * @param {*} value
       */
      setStyle: function (key, value) {
          this.style.set(key, value);
          this.dirty(false);
          return this;
      },

      /**
       * Use given style object
       * @param  {Object} obj
       */
      useStyle: function (obj) {
          this.style = new Style(obj, this);
          this.dirty(false);
          return this;
      }
  };

  inherits(Displayable, Element);

  mixin(Displayable, RectText);

  /**
   * @alias zrender/graphic/Image
   * @extends module:zrender/graphic/Displayable
   * @constructor
   * @param {Object} opts
   */
  function ZImage(opts) {
      Displayable.call(this, opts);
  }

  ZImage.prototype = {

      constructor: ZImage,

      type: 'image',

      brush: function (ctx, prevEl) {
          var style = this.style;
          var src = style.image;

          // Must bind each time
          style.bind(ctx, this, prevEl);

          var image = this._image = createOrUpdateImage(
              src,
              this._image,
              this,
              this.onload
          );

          if (!image || !isImageReady(image)) {
              return;
          }

          // 图片已经加载完成
          // if (image.nodeName.toUpperCase() == 'IMG') {
          //     if (!image.complete) {
          //         return;
          //     }
          // }
          // Else is canvas

          var x = style.x || 0;
          var y = style.y || 0;
          var width = style.width;
          var height = style.height;
          var aspect = image.width / image.height;
          if (width == null && height != null) {
              // Keep image/height ratio
              width = height * aspect;
          }
          else if (height == null && width != null) {
              height = width / aspect;
          }
          else if (width == null && height == null) {
              width = image.width;
              height = image.height;
          }

          // 设置transform
          this.setTransform(ctx);

          if (style.sWidth && style.sHeight) {
              var sx = style.sx || 0;
              var sy = style.sy || 0;
              ctx.drawImage(
                  image,
                  sx, sy, style.sWidth, style.sHeight,
                  x, y, width, height
              );
          }
          else if (style.sx && style.sy) {
              var sx = style.sx;
              var sy = style.sy;
              var sWidth = width - sx;
              var sHeight = height - sy;
              ctx.drawImage(
                  image,
                  sx, sy, sWidth, sHeight,
                  x, y, width, height
              );
          }
          else {
              ctx.drawImage(image, x, y, width, height);
          }

          // Draw rect text
          if (style.text != null) {
              // Only restore transform when needs draw text.
              this.restoreTransform(ctx);
              this.drawRectText(ctx, this.getBoundingRect());
          }
      },

      getBoundingRect: function () {
          var style = this.style;
          if (!this._rect) {
              this._rect = new BoundingRect(
                  style.x || 0, style.y || 0, style.width || 0, style.height || 0
              );
          }
          return this._rect;
      }
  };

  inherits(ZImage, Displayable);

  // 22.1.3.31 Array.prototype[@@unscopables]
  var UNSCOPABLES = _wks('unscopables');
  var ArrayProto = Array.prototype;
  if (ArrayProto[UNSCOPABLES] == undefined) _hide(ArrayProto, UNSCOPABLES, {});
  var _addToUnscopables = function (key) {
    ArrayProto[UNSCOPABLES][key] = true;
  };

  var _iterStep = function (done, value) {
    return { value: value, done: !!done };
  };

  var _iterators = {};

  // to indexed object, toObject with fallback for non-array-like ES3 strings


  var _toIobject = function (it) {
    return _iobject(_defined(it));
  };

  var max$1 = Math.max;
  var min$2 = Math.min;
  var _toAbsoluteIndex = function (index, length) {
    index = _toInteger(index);
    return index < 0 ? max$1(index + length, 0) : min$2(index, length);
  };

  // false -> Array#indexOf
  // true  -> Array#includes



  var _arrayIncludes = function (IS_INCLUDES) {
    return function ($this, el, fromIndex) {
      var O = _toIobject($this);
      var length = _toLength(O.length);
      var index = _toAbsoluteIndex(fromIndex, length);
      var value;
      // Array#includes uses SameValueZero equality algorithm
      // eslint-disable-next-line no-self-compare
      if (IS_INCLUDES && el != el) while (length > index) {
        value = O[index++];
        // eslint-disable-next-line no-self-compare
        if (value != value) return true;
      // Array#indexOf ignores holes, Array#includes - not
      } else for (;length > index; index++) if (IS_INCLUDES || index in O) {
        if (O[index] === el) return IS_INCLUDES || index || 0;
      } return !IS_INCLUDES && -1;
    };
  };

  var shared = _shared('keys');

  var _sharedKey = function (key) {
    return shared[key] || (shared[key] = _uid(key));
  };

  var arrayIndexOf = _arrayIncludes(false);
  var IE_PROTO = _sharedKey('IE_PROTO');

  var _objectKeysInternal = function (object, names) {
    var O = _toIobject(object);
    var i = 0;
    var result = [];
    var key;
    for (key in O) if (key != IE_PROTO) _has(O, key) && result.push(key);
    // Don't enum bug & hidden keys
    while (names.length > i) if (_has(O, key = names[i++])) {
      ~arrayIndexOf(result, key) || result.push(key);
    }
    return result;
  };

  // IE 8- don't enum bug keys
  var _enumBugKeys = (
    'constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf'
  ).split(',');

  // 19.1.2.14 / 15.2.3.14 Object.keys(O)



  var _objectKeys = Object.keys || function keys(O) {
    return _objectKeysInternal(O, _enumBugKeys);
  };

  var _objectDps = _descriptors ? Object.defineProperties : function defineProperties(O, Properties) {
    _anObject(O);
    var keys = _objectKeys(Properties);
    var length = keys.length;
    var i = 0;
    var P;
    while (length > i) _objectDp.f(O, P = keys[i++], Properties[P]);
    return O;
  };

  var document$2 = _global.document;
  var _html = document$2 && document$2.documentElement;

  // 19.1.2.2 / 15.2.3.5 Object.create(O [, Properties])



  var IE_PROTO$1 = _sharedKey('IE_PROTO');
  var Empty = function () { /* empty */ };
  var PROTOTYPE$1 = 'prototype';

  // Create object with fake `null` prototype: use iframe Object with cleared prototype
  var createDict = function () {
    // Thrash, waste and sodomy: IE GC bug
    var iframe = _domCreate('iframe');
    var i = _enumBugKeys.length;
    var lt = '<';
    var gt = '>';
    var iframeDocument;
    iframe.style.display = 'none';
    _html.appendChild(iframe);
    iframe.src = 'javascript:'; // eslint-disable-line no-script-url
    // createDict = iframe.contentWindow.Object;
    // html.removeChild(iframe);
    iframeDocument = iframe.contentWindow.document;
    iframeDocument.open();
    iframeDocument.write(lt + 'script' + gt + 'document.F=Object' + lt + '/script' + gt);
    iframeDocument.close();
    createDict = iframeDocument.F;
    while (i--) delete createDict[PROTOTYPE$1][_enumBugKeys[i]];
    return createDict();
  };

  var _objectCreate = Object.create || function create(O, Properties) {
    var result;
    if (O !== null) {
      Empty[PROTOTYPE$1] = _anObject(O);
      result = new Empty();
      Empty[PROTOTYPE$1] = null;
      // add "__proto__" for Object.getPrototypeOf polyfill
      result[IE_PROTO$1] = O;
    } else result = createDict();
    return Properties === undefined ? result : _objectDps(result, Properties);
  };

  var def = _objectDp.f;

  var TAG = _wks('toStringTag');

  var _setToStringTag = function (it, tag, stat) {
    if (it && !_has(it = stat ? it : it.prototype, TAG)) def(it, TAG, { configurable: true, value: tag });
  };

  var IteratorPrototype = {};

  // 25.1.2.1.1 %IteratorPrototype%[@@iterator]()
  _hide(IteratorPrototype, _wks('iterator'), function () { return this; });

  var _iterCreate = function (Constructor, NAME, next) {
    Constructor.prototype = _objectCreate(IteratorPrototype, { next: _propertyDesc(1, next) });
    _setToStringTag(Constructor, NAME + ' Iterator');
  };

  // 19.1.2.9 / 15.2.3.2 Object.getPrototypeOf(O)


  var IE_PROTO$2 = _sharedKey('IE_PROTO');
  var ObjectProto = Object.prototype;

  var _objectGpo = Object.getPrototypeOf || function (O) {
    O = _toObject(O);
    if (_has(O, IE_PROTO$2)) return O[IE_PROTO$2];
    if (typeof O.constructor == 'function' && O instanceof O.constructor) {
      return O.constructor.prototype;
    } return O instanceof Object ? ObjectProto : null;
  };

  var ITERATOR = _wks('iterator');
  var BUGGY = !([].keys && 'next' in [].keys()); // Safari has buggy iterators w/o `next`
  var FF_ITERATOR = '@@iterator';
  var KEYS = 'keys';
  var VALUES = 'values';

  var returnThis = function () { return this; };

  var _iterDefine = function (Base, NAME, Constructor, next, DEFAULT, IS_SET, FORCED) {
    _iterCreate(Constructor, NAME, next);
    var getMethod = function (kind) {
      if (!BUGGY && kind in proto) return proto[kind];
      switch (kind) {
        case KEYS: return function keys() { return new Constructor(this, kind); };
        case VALUES: return function values() { return new Constructor(this, kind); };
      } return function entries() { return new Constructor(this, kind); };
    };
    var TAG = NAME + ' Iterator';
    var DEF_VALUES = DEFAULT == VALUES;
    var VALUES_BUG = false;
    var proto = Base.prototype;
    var $native = proto[ITERATOR] || proto[FF_ITERATOR] || DEFAULT && proto[DEFAULT];
    var $default = $native || getMethod(DEFAULT);
    var $entries = DEFAULT ? !DEF_VALUES ? $default : getMethod('entries') : undefined;
    var $anyNative = NAME == 'Array' ? proto.entries || $native : $native;
    var methods, key, IteratorPrototype;
    // Fix native
    if ($anyNative) {
      IteratorPrototype = _objectGpo($anyNative.call(new Base()));
      if (IteratorPrototype !== Object.prototype && IteratorPrototype.next) {
        // Set @@toStringTag to native iterators
        _setToStringTag(IteratorPrototype, TAG, true);
        // fix for some old engines
        if ( typeof IteratorPrototype[ITERATOR] != 'function') _hide(IteratorPrototype, ITERATOR, returnThis);
      }
    }
    // fix Array#{values, @@iterator}.name in V8 / FF
    if (DEF_VALUES && $native && $native.name !== VALUES) {
      VALUES_BUG = true;
      $default = function values() { return $native.call(this); };
    }
    // Define iterator
    if ( (BUGGY || VALUES_BUG || !proto[ITERATOR])) {
      _hide(proto, ITERATOR, $default);
    }
    // Plug for library
    _iterators[NAME] = $default;
    _iterators[TAG] = returnThis;
    if (DEFAULT) {
      methods = {
        values: DEF_VALUES ? $default : getMethod(VALUES),
        keys: IS_SET ? $default : getMethod(KEYS),
        entries: $entries
      };
      if (FORCED) for (key in methods) {
        if (!(key in proto)) _redefine(proto, key, methods[key]);
      } else _export(_export.P + _export.F * (BUGGY || VALUES_BUG), NAME, methods);
    }
    return methods;
  };

  // 22.1.3.4 Array.prototype.entries()
  // 22.1.3.13 Array.prototype.keys()
  // 22.1.3.29 Array.prototype.values()
  // 22.1.3.30 Array.prototype[@@iterator]()
  var es6_array_iterator = _iterDefine(Array, 'Array', function (iterated, kind) {
    this._t = _toIobject(iterated); // target
    this._i = 0;                   // next index
    this._k = kind;                // kind
  // 22.1.5.2.1 %ArrayIteratorPrototype%.next()
  }, function () {
    var O = this._t;
    var kind = this._k;
    var index = this._i++;
    if (!O || index >= O.length) {
      this._t = undefined;
      return _iterStep(1);
    }
    if (kind == 'keys') return _iterStep(0, index);
    if (kind == 'values') return _iterStep(0, O[index]);
    return _iterStep(0, [index, O[index]]);
  }, 'values');

  // argumentsList[@@iterator] is %ArrayProto_values% (9.4.4.6, 9.4.4.7)
  _iterators.Arguments = _iterators.Array;

  _addToUnscopables('keys');
  _addToUnscopables('values');
  _addToUnscopables('entries');

  var ITERATOR$1 = _wks('iterator');
  var TO_STRING_TAG = _wks('toStringTag');
  var ArrayValues = _iterators.Array;

  var DOMIterables = {
    CSSRuleList: true, // TODO: Not spec compliant, should be false.
    CSSStyleDeclaration: false,
    CSSValueList: false,
    ClientRectList: false,
    DOMRectList: false,
    DOMStringList: false,
    DOMTokenList: true,
    DataTransferItemList: false,
    FileList: false,
    HTMLAllCollection: false,
    HTMLCollection: false,
    HTMLFormElement: false,
    HTMLSelectElement: false,
    MediaList: true, // TODO: Not spec compliant, should be false.
    MimeTypeArray: false,
    NamedNodeMap: false,
    NodeList: true,
    PaintRequestList: false,
    Plugin: false,
    PluginArray: false,
    SVGLengthList: false,
    SVGNumberList: false,
    SVGPathSegList: false,
    SVGPointList: false,
    SVGStringList: false,
    SVGTransformList: false,
    SourceBufferList: false,
    StyleSheetList: true, // TODO: Not spec compliant, should be false.
    TextTrackCueList: false,
    TextTrackList: false,
    TouchList: false
  };

  for (var collections = _objectKeys(DOMIterables), i$1 = 0; i$1 < collections.length; i$1++) {
    var NAME$1 = collections[i$1];
    var explicit = DOMIterables[NAME$1];
    var Collection = _global[NAME$1];
    var proto = Collection && Collection.prototype;
    var key;
    if (proto) {
      if (!proto[ITERATOR$1]) _hide(proto, ITERATOR$1, ArrayValues);
      if (!proto[TO_STRING_TAG]) _hide(proto, TO_STRING_TAG, NAME$1);
      _iterators[NAME$1] = ArrayValues;
      if (explicit) for (key in es6_array_iterator) if (!proto[key]) _redefine(proto, key, es6_array_iterator[key], true);
    }
  }

  // getting tag from 19.1.3.6 Object.prototype.toString()

  var TAG$1 = _wks('toStringTag');
  // ES3 wrong here
  var ARG = _cof(function () { return arguments; }()) == 'Arguments';

  // fallback for IE11 Script Access Denied error
  var tryGet = function (it, key) {
    try {
      return it[key];
    } catch (e) { /* empty */ }
  };

  var _classof = function (it) {
    var O, T, B;
    return it === undefined ? 'Undefined' : it === null ? 'Null'
      // @@toStringTag case
      : typeof (T = tryGet(O = Object(it), TAG$1)) == 'string' ? T
      // builtinTag case
      : ARG ? _cof(O)
      // ES3 arguments fallback
      : (B = _cof(O)) == 'Object' && typeof O.callee == 'function' ? 'Arguments' : B;
  };

  // 19.1.3.6 Object.prototype.toString()

  var test = {};
  test[_wks('toStringTag')] = 'z';
  if (test + '' != '[object z]') {
    _redefine(Object.prototype, 'toString', function toString() {
      return '[object ' + _classof(this) + ']';
    }, true);
  }

  // true  -> String#at
  // false -> String#codePointAt
  var _stringAt = function (TO_STRING) {
    return function (that, pos) {
      var s = String(_defined(that));
      var i = _toInteger(pos);
      var l = s.length;
      var a, b;
      if (i < 0 || i >= l) return TO_STRING ? '' : undefined;
      a = s.charCodeAt(i);
      return a < 0xd800 || a > 0xdbff || i + 1 === l || (b = s.charCodeAt(i + 1)) < 0xdc00 || b > 0xdfff
        ? TO_STRING ? s.charAt(i) : a
        : TO_STRING ? s.slice(i, i + 2) : (a - 0xd800 << 10) + (b - 0xdc00) + 0x10000;
    };
  };

  var $at = _stringAt(true);

  // 21.1.3.27 String.prototype[@@iterator]()
  _iterDefine(String, 'String', function (iterated) {
    this._t = String(iterated); // target
    this._i = 0;                // next index
  // 21.1.5.2.1 %StringIteratorPrototype%.next()
  }, function () {
    var O = this._t;
    var index = this._i;
    var point;
    if (index >= O.length) return { value: undefined, done: true };
    point = $at(O, index);
    this._i += point.length;
    return { value: point, done: false };
  });

  var _redefineAll = function (target, src, safe) {
    for (var key in src) _redefine(target, key, src[key], safe);
    return target;
  };

  var _anInstance = function (it, Constructor, name, forbiddenField) {
    if (!(it instanceof Constructor) || (forbiddenField !== undefined && forbiddenField in it)) {
      throw TypeError(name + ': incorrect invocation!');
    } return it;
  };

  // call something on iterator step with safe closing on error

  var _iterCall = function (iterator, fn, value, entries) {
    try {
      return entries ? fn(_anObject(value)[0], value[1]) : fn(value);
    // 7.4.6 IteratorClose(iterator, completion)
    } catch (e) {
      var ret = iterator['return'];
      if (ret !== undefined) _anObject(ret.call(iterator));
      throw e;
    }
  };

  // check on default Array iterator

  var ITERATOR$2 = _wks('iterator');
  var ArrayProto$1 = Array.prototype;

  var _isArrayIter = function (it) {
    return it !== undefined && (_iterators.Array === it || ArrayProto$1[ITERATOR$2] === it);
  };

  var ITERATOR$3 = _wks('iterator');

  var core_getIteratorMethod = _core.getIteratorMethod = function (it) {
    if (it != undefined) return it[ITERATOR$3]
      || it['@@iterator']
      || _iterators[_classof(it)];
  };

  var _forOf = createCommonjsModule(function (module) {
  var BREAK = {};
  var RETURN = {};
  var exports = module.exports = function (iterable, entries, fn, that, ITERATOR) {
    var iterFn = ITERATOR ? function () { return iterable; } : core_getIteratorMethod(iterable);
    var f = _ctx(fn, that, entries ? 2 : 1);
    var index = 0;
    var length, step, iterator, result;
    if (typeof iterFn != 'function') throw TypeError(iterable + ' is not iterable!');
    // fast case for arrays with default iterator
    if (_isArrayIter(iterFn)) for (length = _toLength(iterable.length); length > index; index++) {
      result = entries ? f(_anObject(step = iterable[index])[0], step[1]) : f(iterable[index]);
      if (result === BREAK || result === RETURN) return result;
    } else for (iterator = iterFn.call(iterable); !(step = iterator.next()).done;) {
      result = _iterCall(iterator, f, step.value, entries);
      if (result === BREAK || result === RETURN) return result;
    }
  };
  exports.BREAK = BREAK;
  exports.RETURN = RETURN;
  });

  var SPECIES$1 = _wks('species');

  var _setSpecies = function (KEY) {
    var C = _global[KEY];
    if (_descriptors && C && !C[SPECIES$1]) _objectDp.f(C, SPECIES$1, {
      configurable: true,
      get: function () { return this; }
    });
  };

  var _meta = createCommonjsModule(function (module) {
  var META = _uid('meta');


  var setDesc = _objectDp.f;
  var id = 0;
  var isExtensible = Object.isExtensible || function () {
    return true;
  };
  var FREEZE = !_fails(function () {
    return isExtensible(Object.preventExtensions({}));
  });
  var setMeta = function (it) {
    setDesc(it, META, { value: {
      i: 'O' + ++id, // object ID
      w: {}          // weak collections IDs
    } });
  };
  var fastKey = function (it, create) {
    // return primitive with prefix
    if (!_isObject(it)) return typeof it == 'symbol' ? it : (typeof it == 'string' ? 'S' : 'P') + it;
    if (!_has(it, META)) {
      // can't set metadata to uncaught frozen object
      if (!isExtensible(it)) return 'F';
      // not necessary to add metadata
      if (!create) return 'E';
      // add missing metadata
      setMeta(it);
    // return object ID
    } return it[META].i;
  };
  var getWeak = function (it, create) {
    if (!_has(it, META)) {
      // can't set metadata to uncaught frozen object
      if (!isExtensible(it)) return true;
      // not necessary to add metadata
      if (!create) return false;
      // add missing metadata
      setMeta(it);
    // return hash weak collections IDs
    } return it[META].w;
  };
  // add metadata on freeze-family methods calling
  var onFreeze = function (it) {
    if (FREEZE && meta.NEED && isExtensible(it) && !_has(it, META)) setMeta(it);
    return it;
  };
  var meta = module.exports = {
    KEY: META,
    NEED: false,
    fastKey: fastKey,
    getWeak: getWeak,
    onFreeze: onFreeze
  };
  });
  var _meta_1 = _meta.KEY;
  var _meta_2 = _meta.NEED;
  var _meta_3 = _meta.fastKey;
  var _meta_4 = _meta.getWeak;
  var _meta_5 = _meta.onFreeze;

  var _validateCollection = function (it, TYPE) {
    if (!_isObject(it) || it._t !== TYPE) throw TypeError('Incompatible receiver, ' + TYPE + ' required!');
    return it;
  };

  var dP$2 = _objectDp.f;









  var fastKey = _meta.fastKey;

  var SIZE = _descriptors ? '_s' : 'size';

  var getEntry = function (that, key) {
    // fast case
    var index = fastKey(key);
    var entry;
    if (index !== 'F') return that._i[index];
    // frozen object case
    for (entry = that._f; entry; entry = entry.n) {
      if (entry.k == key) return entry;
    }
  };

  var _collectionStrong = {
    getConstructor: function (wrapper, NAME, IS_MAP, ADDER) {
      var C = wrapper(function (that, iterable) {
        _anInstance(that, C, NAME, '_i');
        that._t = NAME;         // collection type
        that._i = _objectCreate(null); // index
        that._f = undefined;    // first entry
        that._l = undefined;    // last entry
        that[SIZE] = 0;         // size
        if (iterable != undefined) _forOf(iterable, IS_MAP, that[ADDER], that);
      });
      _redefineAll(C.prototype, {
        // 23.1.3.1 Map.prototype.clear()
        // 23.2.3.2 Set.prototype.clear()
        clear: function clear() {
          for (var that = _validateCollection(this, NAME), data = that._i, entry = that._f; entry; entry = entry.n) {
            entry.r = true;
            if (entry.p) entry.p = entry.p.n = undefined;
            delete data[entry.i];
          }
          that._f = that._l = undefined;
          that[SIZE] = 0;
        },
        // 23.1.3.3 Map.prototype.delete(key)
        // 23.2.3.4 Set.prototype.delete(value)
        'delete': function (key) {
          var that = _validateCollection(this, NAME);
          var entry = getEntry(that, key);
          if (entry) {
            var next = entry.n;
            var prev = entry.p;
            delete that._i[entry.i];
            entry.r = true;
            if (prev) prev.n = next;
            if (next) next.p = prev;
            if (that._f == entry) that._f = next;
            if (that._l == entry) that._l = prev;
            that[SIZE]--;
          } return !!entry;
        },
        // 23.2.3.6 Set.prototype.forEach(callbackfn, thisArg = undefined)
        // 23.1.3.5 Map.prototype.forEach(callbackfn, thisArg = undefined)
        forEach: function forEach(callbackfn /* , that = undefined */) {
          _validateCollection(this, NAME);
          var f = _ctx(callbackfn, arguments.length > 1 ? arguments[1] : undefined, 3);
          var entry;
          while (entry = entry ? entry.n : this._f) {
            f(entry.v, entry.k, this);
            // revert to the last existing entry
            while (entry && entry.r) entry = entry.p;
          }
        },
        // 23.1.3.7 Map.prototype.has(key)
        // 23.2.3.7 Set.prototype.has(value)
        has: function has(key) {
          return !!getEntry(_validateCollection(this, NAME), key);
        }
      });
      if (_descriptors) dP$2(C.prototype, 'size', {
        get: function () {
          return _validateCollection(this, NAME)[SIZE];
        }
      });
      return C;
    },
    def: function (that, key, value) {
      var entry = getEntry(that, key);
      var prev, index;
      // change existing entry
      if (entry) {
        entry.v = value;
      // create new entry
      } else {
        that._l = entry = {
          i: index = fastKey(key, true), // <- index
          k: key,                        // <- key
          v: value,                      // <- value
          p: prev = that._l,             // <- previous entry
          n: undefined,                  // <- next entry
          r: false                       // <- removed
        };
        if (!that._f) that._f = entry;
        if (prev) prev.n = entry;
        that[SIZE]++;
        // add to index
        if (index !== 'F') that._i[index] = entry;
      } return that;
    },
    getEntry: getEntry,
    setStrong: function (C, NAME, IS_MAP) {
      // add .keys, .values, .entries, [@@iterator]
      // 23.1.3.4, 23.1.3.8, 23.1.3.11, 23.1.3.12, 23.2.3.5, 23.2.3.8, 23.2.3.10, 23.2.3.11
      _iterDefine(C, NAME, function (iterated, kind) {
        this._t = _validateCollection(iterated, NAME); // target
        this._k = kind;                     // kind
        this._l = undefined;                // previous
      }, function () {
        var that = this;
        var kind = that._k;
        var entry = that._l;
        // revert to the last existing entry
        while (entry && entry.r) entry = entry.p;
        // get next entry
        if (!that._t || !(that._l = entry = entry ? entry.n : that._t._f)) {
          // or finish the iteration
          that._t = undefined;
          return _iterStep(1);
        }
        // return step by kind
        if (kind == 'keys') return _iterStep(0, entry.k);
        if (kind == 'values') return _iterStep(0, entry.v);
        return _iterStep(0, [entry.k, entry.v]);
      }, IS_MAP ? 'entries' : 'values', !IS_MAP, true);

      // add [@@species], 23.1.2.2, 23.2.2.2
      _setSpecies(NAME);
    }
  };

  var ITERATOR$4 = _wks('iterator');
  var SAFE_CLOSING = false;

  try {
    var riter = [7][ITERATOR$4]();
    riter['return'] = function () { SAFE_CLOSING = true; };
    // eslint-disable-next-line no-throw-literal
    Array.from(riter, function () { throw 2; });
  } catch (e) { /* empty */ }

  var _iterDetect = function (exec, skipClosing) {
    if (!skipClosing && !SAFE_CLOSING) return false;
    var safe = false;
    try {
      var arr = [7];
      var iter = arr[ITERATOR$4]();
      iter.next = function () { return { done: safe = true }; };
      arr[ITERATOR$4] = function () { return iter; };
      exec(arr);
    } catch (e) { /* empty */ }
    return safe;
  };

  var f$1 = {}.propertyIsEnumerable;

  var _objectPie = {
  	f: f$1
  };

  var gOPD = Object.getOwnPropertyDescriptor;

  var f$2 = _descriptors ? gOPD : function getOwnPropertyDescriptor(O, P) {
    O = _toIobject(O);
    P = _toPrimitive(P, true);
    if (_ie8DomDefine) try {
      return gOPD(O, P);
    } catch (e) { /* empty */ }
    if (_has(O, P)) return _propertyDesc(!_objectPie.f.call(O, P), O[P]);
  };

  var _objectGopd = {
  	f: f$2
  };

  // Works with __proto__ only. Old v8 can't work with null proto objects.
  /* eslint-disable no-proto */


  var check = function (O, proto) {
    _anObject(O);
    if (!_isObject(proto) && proto !== null) throw TypeError(proto + ": can't set as prototype!");
  };
  var _setProto = {
    set: Object.setPrototypeOf || ('__proto__' in {} ? // eslint-disable-line
      function (test, buggy, set) {
        try {
          set = _ctx(Function.call, _objectGopd.f(Object.prototype, '__proto__').set, 2);
          set(test, []);
          buggy = !(test instanceof Array);
        } catch (e) { buggy = true; }
        return function setPrototypeOf(O, proto) {
          check(O, proto);
          if (buggy) O.__proto__ = proto;
          else set(O, proto);
          return O;
        };
      }({}, false) : undefined),
    check: check
  };

  var setPrototypeOf = _setProto.set;
  var _inheritIfRequired = function (that, target, C) {
    var S = target.constructor;
    var P;
    if (S !== C && typeof S == 'function' && (P = S.prototype) !== C.prototype && _isObject(P) && setPrototypeOf) {
      setPrototypeOf(that, P);
    } return that;
  };

  var _collection = function (NAME, wrapper, methods, common, IS_MAP, IS_WEAK) {
    var Base = _global[NAME];
    var C = Base;
    var ADDER = IS_MAP ? 'set' : 'add';
    var proto = C && C.prototype;
    var O = {};
    var fixMethod = function (KEY) {
      var fn = proto[KEY];
      _redefine(proto, KEY,
        KEY == 'delete' ? function (a) {
          return IS_WEAK && !_isObject(a) ? false : fn.call(this, a === 0 ? 0 : a);
        } : KEY == 'has' ? function has(a) {
          return IS_WEAK && !_isObject(a) ? false : fn.call(this, a === 0 ? 0 : a);
        } : KEY == 'get' ? function get(a) {
          return IS_WEAK && !_isObject(a) ? undefined : fn.call(this, a === 0 ? 0 : a);
        } : KEY == 'add' ? function add(a) { fn.call(this, a === 0 ? 0 : a); return this; }
          : function set(a, b) { fn.call(this, a === 0 ? 0 : a, b); return this; }
      );
    };
    if (typeof C != 'function' || !(IS_WEAK || proto.forEach && !_fails(function () {
      new C().entries().next();
    }))) {
      // create collection constructor
      C = common.getConstructor(wrapper, NAME, IS_MAP, ADDER);
      _redefineAll(C.prototype, methods);
      _meta.NEED = true;
    } else {
      var instance = new C();
      // early implementations not supports chaining
      var HASNT_CHAINING = instance[ADDER](IS_WEAK ? {} : -0, 1) != instance;
      // V8 ~  Chromium 40- weak-collections throws on primitives, but should return false
      var THROWS_ON_PRIMITIVES = _fails(function () { instance.has(1); });
      // most early implementations doesn't supports iterables, most modern - not close it correctly
      var ACCEPT_ITERABLES = _iterDetect(function (iter) { new C(iter); }); // eslint-disable-line no-new
      // for early implementations -0 and +0 not the same
      var BUGGY_ZERO = !IS_WEAK && _fails(function () {
        // V8 ~ Chromium 42- fails only with 5+ elements
        var $instance = new C();
        var index = 5;
        while (index--) $instance[ADDER](index, index);
        return !$instance.has(-0);
      });
      if (!ACCEPT_ITERABLES) {
        C = wrapper(function (target, iterable) {
          _anInstance(target, C, NAME);
          var that = _inheritIfRequired(new Base(), target, C);
          if (iterable != undefined) _forOf(iterable, IS_MAP, that[ADDER], that);
          return that;
        });
        C.prototype = proto;
        proto.constructor = C;
      }
      if (THROWS_ON_PRIMITIVES || BUGGY_ZERO) {
        fixMethod('delete');
        fixMethod('has');
        IS_MAP && fixMethod('get');
      }
      if (BUGGY_ZERO || HASNT_CHAINING) fixMethod(ADDER);
      // weak collections should not contains .clear method
      if (IS_WEAK && proto.clear) delete proto.clear;
    }

    _setToStringTag(C, NAME);

    O[NAME] = C;
    _export(_export.G + _export.W + _export.F * (C != Base), O);

    if (!IS_WEAK) common.setStrong(C, NAME, IS_MAP);

    return C;
  };

  var MAP = 'Map';

  // 23.1 Map Objects
  var es6_map = _collection(MAP, function (get) {
    return function Map() { return get(this, arguments.length > 0 ? arguments[0] : undefined); };
  }, {
    // 23.1.3.6 Map.prototype.get(key)
    get: function get(key) {
      var entry = _collectionStrong.getEntry(_validateCollection(this, MAP), key);
      return entry && entry.v;
    },
    // 23.1.3.9 Map.prototype.set(key, value)
    set: function set(key, value) {
      return _collectionStrong.def(_validateCollection(this, MAP), key === 0 ? 0 : key, value);
    }
  }, _collectionStrong, true);

  /**
   * echarts设备环境识别
   *
   * @desc echarts基于Canvas，纯Javascript图表库，提供直观，生动，可交互，可个性化定制的数据统计图表。
   * @author firede[firede@firede.us]
   * @desc thanks zepto.
   */

  var env = {};

  if (typeof wx === 'object' && typeof wx.getSystemInfoSync === 'function') {
      // In Weixin Application
      env = {
          browser: {},
          os: {},
          node: false,
          wxa: true, // Weixin Application
          canvasSupported: true,
          svgSupported: false,
          touchEventsSupported: true,
          domSupported: false
      };
  }
  else if (typeof document === 'undefined' && typeof self !== 'undefined') {
      // In worker
      env = {
          browser: {},
          os: {},
          node: false,
          worker: true,
          canvasSupported: true,
          domSupported: false
      };
  }
  else if (typeof navigator === 'undefined') {
      // In node
      env = {
          browser: {},
          os: {},
          node: true,
          worker: false,
          // Assume canvas is supported
          canvasSupported: true,
          svgSupported: true,
          domSupported: false
      };
  }
  else {
      env = detect(navigator.userAgent);
  }

  var env$1 = env;

  // Zepto.js
  // (c) 2010-2013 Thomas Fuchs
  // Zepto.js may be freely distributed under the MIT license.

  function detect(ua) {
      var os = {};
      var browser = {};
      // var webkit = ua.match(/Web[kK]it[\/]{0,1}([\d.]+)/);
      // var android = ua.match(/(Android);?[\s\/]+([\d.]+)?/);
      // var ipad = ua.match(/(iPad).*OS\s([\d_]+)/);
      // var ipod = ua.match(/(iPod)(.*OS\s([\d_]+))?/);
      // var iphone = !ipad && ua.match(/(iPhone\sOS)\s([\d_]+)/);
      // var webos = ua.match(/(webOS|hpwOS)[\s\/]([\d.]+)/);
      // var touchpad = webos && ua.match(/TouchPad/);
      // var kindle = ua.match(/Kindle\/([\d.]+)/);
      // var silk = ua.match(/Silk\/([\d._]+)/);
      // var blackberry = ua.match(/(BlackBerry).*Version\/([\d.]+)/);
      // var bb10 = ua.match(/(BB10).*Version\/([\d.]+)/);
      // var rimtabletos = ua.match(/(RIM\sTablet\sOS)\s([\d.]+)/);
      // var playbook = ua.match(/PlayBook/);
      // var chrome = ua.match(/Chrome\/([\d.]+)/) || ua.match(/CriOS\/([\d.]+)/);
      var firefox = ua.match(/Firefox\/([\d.]+)/);
      // var safari = webkit && ua.match(/Mobile\//) && !chrome;
      // var webview = ua.match(/(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)/) && !chrome;
      var ie = ua.match(/MSIE\s([\d.]+)/)
          // IE 11 Trident/7.0; rv:11.0
          || ua.match(/Trident\/.+?rv:(([\d.]+))/);
      var edge = ua.match(/Edge\/([\d.]+)/); // IE 12 and 12+

      var weChat = (/micromessenger/i).test(ua);

      // Todo: clean this up with a better OS/browser seperation:
      // - discern (more) between multiple browsers on android
      // - decide if kindle fire in silk mode is android or not
      // - Firefox on Android doesn't specify the Android version
      // - possibly devide in os, device and browser hashes

      // if (browser.webkit = !!webkit) browser.version = webkit[1];

      // if (android) os.android = true, os.version = android[2];
      // if (iphone && !ipod) os.ios = os.iphone = true, os.version = iphone[2].replace(/_/g, '.');
      // if (ipad) os.ios = os.ipad = true, os.version = ipad[2].replace(/_/g, '.');
      // if (ipod) os.ios = os.ipod = true, os.version = ipod[3] ? ipod[3].replace(/_/g, '.') : null;
      // if (webos) os.webos = true, os.version = webos[2];
      // if (touchpad) os.touchpad = true;
      // if (blackberry) os.blackberry = true, os.version = blackberry[2];
      // if (bb10) os.bb10 = true, os.version = bb10[2];
      // if (rimtabletos) os.rimtabletos = true, os.version = rimtabletos[2];
      // if (playbook) browser.playbook = true;
      // if (kindle) os.kindle = true, os.version = kindle[1];
      // if (silk) browser.silk = true, browser.version = silk[1];
      // if (!silk && os.android && ua.match(/Kindle Fire/)) browser.silk = true;
      // if (chrome) browser.chrome = true, browser.version = chrome[1];
      if (firefox) {
          browser.firefox = true;
          browser.version = firefox[1];
      }
      // if (safari && (ua.match(/Safari/) || !!os.ios)) browser.safari = true;
      // if (webview) browser.webview = true;

      if (ie) {
          browser.ie = true;
          browser.version = ie[1];
      }

      if (edge) {
          browser.edge = true;
          browser.version = edge[1];
      }

      // It is difficult to detect WeChat in Win Phone precisely, because ua can
      // not be set on win phone. So we do not consider Win Phone.
      if (weChat) {
          browser.weChat = true;
      }

      // os.tablet = !!(ipad || playbook || (android && !ua.match(/Mobile/)) ||
      //     (firefox && ua.match(/Tablet/)) || (ie && !ua.match(/Phone/) && ua.match(/Touch/)));
      // os.phone  = !!(!os.tablet && !os.ipod && (android || iphone || webos ||
      //     (chrome && ua.match(/Android/)) || (chrome && ua.match(/CriOS\/([\d.]+)/)) ||
      //     (firefox && ua.match(/Mobile/)) || (ie && ua.match(/Touch/))));

      return {
          browser: browser,
          os: os,
          node: false,
          // 原生canvas支持，改极端点了
          // canvasSupported : !(browser.ie && parseFloat(browser.version) < 9)
          canvasSupported: !!document.createElement('canvas').getContext,
          svgSupported: typeof SVGRect !== 'undefined',
          // works on most browsers
          // IE10/11 does not support touch event, and MS Edge supports them but not by
          // default, so we dont check navigator.maxTouchPoints for them here.
          touchEventsSupported: 'ontouchstart' in window && !browser.ie && !browser.edge,
          // <http://caniuse.com/#search=pointer%20event>.
          pointerEventsSupported: 'onpointerdown' in window
              // Firefox supports pointer but not by default, only MS browsers are reliable on pointer
              // events currently. So we dont use that on other browsers unless tested sufficiently.
              // Although IE 10 supports pointer event, it use old style and is different from the
              // standard. So we exclude that. (IE 10 is hardly used on touch device)
              && (browser.edge || (browser.ie && browser.version >= 11)),
          // passiveSupported: detectPassiveSupport()
          domSupported: typeof document !== 'undefined'
      };
  }

  // See https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md#feature-detection
  // function detectPassiveSupport() {
  //     // Test via a getter in the options object to see if the passive property is accessed
  //     var supportsPassive = false;
  //     try {
  //         var opts = Object.defineProperty({}, 'passive', {
  //             get: function() {
  //                 supportsPassive = true;
  //             }
  //         });
  //         window.addEventListener('testPassive', function() {}, opts);
  //     } catch (e) {
  //     }
  //     return supportsPassive;
  // }

  // TODO Draggable for group
  // FIXME Draggable on element which has parent rotation or scale
  function Draggable() {

      this.on('mousedown', this._dragStart, this);
      this.on('mousemove', this._drag, this);
      this.on('mouseup', this._dragEnd, this);
      this.on('globalout', this._dragEnd, this);
      // this._dropTarget = null;
      // this._draggingTarget = null;

      // this._x = 0;
      // this._y = 0;
  }

  Draggable.prototype = {

      constructor: Draggable,

      _dragStart: function (e) {
          var draggingTarget = e.target;
          if (draggingTarget && draggingTarget.draggable) {
              this._draggingTarget = draggingTarget;
              draggingTarget.dragging = true;
              this._x = e.offsetX;
              this._y = e.offsetY;

              this.dispatchToElement(param(draggingTarget, e), 'dragstart', e.event);
          }
      },

      _drag: function (e) {
          var draggingTarget = this._draggingTarget;
          if (draggingTarget) {

              var x = e.offsetX;
              var y = e.offsetY;

              var dx = x - this._x;
              var dy = y - this._y;
              this._x = x;
              this._y = y;

              draggingTarget.drift(dx, dy, e);
              this.dispatchToElement(param(draggingTarget, e), 'drag', e.event);

              var dropTarget = this.findHover(x, y, draggingTarget).target;
              var lastDropTarget = this._dropTarget;
              this._dropTarget = dropTarget;

              if (draggingTarget !== dropTarget) {
                  if (lastDropTarget && dropTarget !== lastDropTarget) {
                      this.dispatchToElement(param(lastDropTarget, e), 'dragleave', e.event);
                  }
                  if (dropTarget && dropTarget !== lastDropTarget) {
                      this.dispatchToElement(param(dropTarget, e), 'dragenter', e.event);
                  }
              }
          }
      },

      _dragEnd: function (e) {
          var draggingTarget = this._draggingTarget;

          if (draggingTarget) {
              draggingTarget.dragging = false;
          }

          this.dispatchToElement(param(draggingTarget, e), 'dragend', e.event);

          if (this._dropTarget) {
              this.dispatchToElement(param(this._dropTarget, e), 'drop', e.event);
          }

          this._draggingTarget = null;
          this._dropTarget = null;
      }

  };

  function param(target, e) {
      return {target: target, topTarget: e && e.topTarget};
  }

  /**
   * 事件辅助类
   * @module zrender/core/event
   * @author Kener (@Kener-林峰, kener.linfeng@gmail.com)
   */

  var isDomLevel2 = (typeof window !== 'undefined') && !!window.addEventListener;

  var MOUSE_EVENT_REG = /^(?:mouse|pointer|contextmenu|drag|drop)|click/;

  function getBoundingClientRect(el) {
      // BlackBerry 5, iOS 3 (original iPhone) don't have getBoundingRect
      return el.getBoundingClientRect ? el.getBoundingClientRect() : {left: 0, top: 0};
  }

  // `calculate` is optional, default false
  function clientToLocal(el, e, out, calculate) {
      out = out || {};

      // According to the W3C Working Draft, offsetX and offsetY should be relative
      // to the padding edge of the target element. The only browser using this convention
      // is IE. Webkit uses the border edge, Opera uses the content edge, and FireFox does
      // not support the properties.
      // (see http://www.jacklmoore.com/notes/mouse-position/)
      // In zr painter.dom, padding edge equals to border edge.

      // FIXME
      // When mousemove event triggered on ec tooltip, target is not zr painter.dom, and
      // offsetX/Y is relative to e.target, where the calculation of zrX/Y via offsetX/Y
      // is too complex. So css-transfrom dont support in this case temporarily.
      if (calculate || !env$1.canvasSupported) {
          defaultGetZrXY(el, e, out);
      }
      // Caution: In FireFox, layerX/layerY Mouse position relative to the closest positioned
      // ancestor element, so we should make sure el is positioned (e.g., not position:static).
      // BTW1, Webkit don't return the same results as FF in non-simple cases (like add
      // zoom-factor, overflow / opacity layers, transforms ...)
      // BTW2, (ev.offsetY || ev.pageY - $(ev.target).offset().top) is not correct in preserve-3d.
      // <https://bugs.jquery.com/ticket/8523#comment:14>
      // BTW3, In ff, offsetX/offsetY is always 0.
      else if (env$1.browser.firefox && e.layerX != null && e.layerX !== e.offsetX) {
          out.zrX = e.layerX;
          out.zrY = e.layerY;
      }
      // For IE6+, chrome, safari, opera. (When will ff support offsetX?)
      else if (e.offsetX != null) {
          out.zrX = e.offsetX;
          out.zrY = e.offsetY;
      }
      // For some other device, e.g., IOS safari.
      else {
          defaultGetZrXY(el, e, out);
      }

      return out;
  }

  function defaultGetZrXY(el, e, out) {
      // This well-known method below does not support css transform.
      var box = getBoundingClientRect(el);
      out.zrX = e.clientX - box.left;
      out.zrY = e.clientY - box.top;
  }

  /**
   * 如果存在第三方嵌入的一些dom触发的事件，或touch事件，需要转换一下事件坐标.
   * `calculate` is optional, default false.
   */
  function normalizeEvent(el, e, calculate) {

      e = e || window.event;

      if (e.zrX != null) {
          return e;
      }

      var eventType = e.type;
      var isTouch = eventType && eventType.indexOf('touch') >= 0;

      if (!isTouch) {
          clientToLocal(el, e, e, calculate);
          e.zrDelta = (e.wheelDelta) ? e.wheelDelta / 120 : -(e.detail || 0) / 3;
      }
      else {
          var touch = eventType !== 'touchend'
              ? e.targetTouches[0]
              : e.changedTouches[0];
          touch && clientToLocal(el, touch, e, calculate);
      }

      // Add which for click: 1 === left; 2 === middle; 3 === right; otherwise: 0;
      // See jQuery: https://github.com/jquery/jquery/blob/master/src/event.js
      // If e.which has been defined, if may be readonly,
      // see: https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/which
      var button = e.button;
      if (e.which == null && button !== undefined && MOUSE_EVENT_REG.test(e.type)) {
          e.which = (button & 1 ? 1 : (button & 2 ? 3 : (button & 4 ? 2 : 0)));
      }
      // [Caution]: `e.which` from browser is not always reliable. For example,
      // when press left button and `mousemove (pointermove)` in Edge, the `e.which`
      // is 65536 and the `e.button` is -1. But the `mouseup (pointerup)` and
      // `mousedown (pointerdown)` is the same as Chrome does.

      return e;
  }

  /**
   * @param {HTMLElement} el
   * @param {string} name
   * @param {Function} handler
   */
  function addEventListener(el, name, handler) {
      if (isDomLevel2) {
          // Reproduct the console warning:
          // [Violation] Added non-passive event listener to a scroll-blocking <some> event.
          // Consider marking event handler as 'passive' to make the page more responsive.
          // Just set console log level: verbose in chrome dev tool.
          // then the warning log will be printed when addEventListener called.
          // See https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md
          // We have not yet found a neat way to using passive. Because in zrender the dom event
          // listener delegate all of the upper events of element. Some of those events need
          // to prevent default. For example, the feature `preventDefaultMouseMove` of echarts.
          // Before passive can be adopted, these issues should be considered:
          // (1) Whether and how a zrender user specifies an event listener passive. And by default,
          // passive or not.
          // (2) How to tread that some zrender event listener is passive, and some is not. If
          // we use other way but not preventDefault of mousewheel and touchmove, browser
          // compatibility should be handled.

          // var opts = (env.passiveSupported && name === 'mousewheel')
          //     ? {passive: true}
          //     // By default, the third param of el.addEventListener is `capture: false`.
          //     : void 0;
          // el.addEventListener(name, handler /* , opts */);
          el.addEventListener(name, handler);
      }
      else {
          el.attachEvent('on' + name, handler);
      }
  }

  function removeEventListener(el, name, handler) {
      if (isDomLevel2) {
          el.removeEventListener(name, handler);
      }
      else {
          el.detachEvent('on' + name, handler);
      }
  }

  /**
   * preventDefault and stopPropagation.
   * Notice: do not do that in zrender. Upper application
   * do that if necessary.
   *
   * @memberOf module:zrender/core/event
   * @method
   * @param {Event} e : event对象
   */
  var stop = isDomLevel2
      ? function (e) {
          e.preventDefault();
          e.stopPropagation();
          e.cancelBubble = true;
      }
      : function (e) {
          e.returnValue = false;
          e.cancelBubble = true;
      };

  /**
   * Only implements needed gestures for mobile.
   */

  var GestureMgr = function () {

      /**
       * @private
       * @type {Array.<Object>}
       */
      this._track = [];
  };

  GestureMgr.prototype = {

      constructor: GestureMgr,

      recognize: function (event, target, root) {
          this._doTrack(event, target, root);
          return this._recognize(event);
      },

      clear: function () {
          this._track.length = 0;
          return this;
      },

      _doTrack: function (event, target, root) {
          var touches = event.touches;

          if (!touches) {
              return;
          }

          var trackItem = {
              points: [],
              touches: [],
              target: target,
              event: event
          };

          for (var i = 0, len = touches.length; i < len; i++) {
              var touch = touches[i];
              var pos = clientToLocal(root, touch, {});
              trackItem.points.push([pos.zrX, pos.zrY]);
              trackItem.touches.push(touch);
          }

          this._track.push(trackItem);
      },

      _recognize: function (event) {
          for (var eventName in recognizers) {
              if (recognizers.hasOwnProperty(eventName)) {
                  var gestureInfo = recognizers[eventName](this._track, event);
                  if (gestureInfo) {
                      return gestureInfo;
                  }
              }
          }
      }
  };

  function dist$1(pointPair) {
      var dx = pointPair[1][0] - pointPair[0][0];
      var dy = pointPair[1][1] - pointPair[0][1];

      return Math.sqrt(dx * dx + dy * dy);
  }

  function center(pointPair) {
      return [
          (pointPair[0][0] + pointPair[1][0]) / 2,
          (pointPair[0][1] + pointPair[1][1]) / 2
      ];
  }

  var recognizers = {

      pinch: function (track, event) {
          var trackLen = track.length;

          if (!trackLen) {
              return;
          }

          var pinchEnd = (track[trackLen - 1] || {}).points;
          var pinchPre = (track[trackLen - 2] || {}).points || pinchEnd;

          if (pinchPre
              && pinchPre.length > 1
              && pinchEnd
              && pinchEnd.length > 1
          ) {
              var pinchScale = dist$1(pinchEnd) / dist$1(pinchPre);
              !isFinite(pinchScale) && (pinchScale = 1);

              event.pinchScale = pinchScale;

              var pinchCenter = center(pinchEnd);
              event.pinchX = pinchCenter[0];
              event.pinchY = pinchCenter[1];

              return {
                  type: 'pinch',
                  target: track[0].target,
                  event: event
              };
          }
      }

      // Only pinch currently.
  };

  var SILENT = 'silent';

  function makeEventPacket(eveType, targetInfo, event) {
      return {
          type: eveType,
          event: event,
          // target can only be an element that is not silent.
          target: targetInfo.target,
          // topTarget can be a silent element.
          topTarget: targetInfo.topTarget,
          cancelBubble: false,
          offsetX: event.zrX,
          offsetY: event.zrY,
          gestureEvent: event.gestureEvent,
          pinchX: event.pinchX,
          pinchY: event.pinchY,
          pinchScale: event.pinchScale,
          wheelDelta: event.zrDelta,
          zrByTouch: event.zrByTouch,
          which: event.which,
          stop: stopEvent
      };
  }

  function stopEvent(event) {
      stop(this.event);
  }

  function EmptyProxy() {}
  EmptyProxy.prototype.dispose = function () {};

  var handlerNames = [
      'click', 'dblclick', 'mousewheel', 'mouseout',
      'mouseup', 'mousedown', 'mousemove', 'contextmenu'
  ];
  /**
   * @alias module:zrender/Handler
   * @constructor
   * @extends module:zrender/mixin/Eventful
   * @param {module:zrender/Storage} storage Storage instance.
   * @param {module:zrender/Painter} painter Painter instance.
   * @param {module:zrender/dom/HandlerProxy} proxy HandlerProxy instance.
   * @param {HTMLElement} painterRoot painter.root (not painter.getViewportRoot()).
   */
  var Handler = function (storage, painter, proxy, painterRoot) {
      Eventful.call(this);

      this.storage = storage;

      this.painter = painter;

      this.painterRoot = painterRoot;

      proxy = proxy || new EmptyProxy();

      /**
       * Proxy of event. can be Dom, WebGLSurface, etc.
       */
      this.proxy = null;

      /**
       * {target, topTarget, x, y}
       * @private
       * @type {Object}
       */
      this._hovered = {};

      /**
       * @private
       * @type {Date}
       */
      this._lastTouchMoment;

      /**
       * @private
       * @type {number}
       */
      this._lastX;

      /**
       * @private
       * @type {number}
       */
      this._lastY;

      /**
       * @private
       * @type {module:zrender/core/GestureMgr}
       */
      this._gestureMgr;


      Draggable.call(this);

      this.setHandlerProxy(proxy);
  };

  Handler.prototype = {

      constructor: Handler,

      setHandlerProxy: function (proxy) {
          if (this.proxy) {
              this.proxy.dispose();
          }

          if (proxy) {
              each(handlerNames, function (name) {
                  proxy.on && proxy.on(name, this[name], this);
              }, this);
              // Attach handler
              proxy.handler = this;
          }
          this.proxy = proxy;
      },

      mousemove: function (event) {
          var x = event.zrX;
          var y = event.zrY;

          var lastHovered = this._hovered;
          var lastHoveredTarget = lastHovered.target;

          // If lastHoveredTarget is removed from zr (detected by '__zr') by some API call
          // (like 'setOption' or 'dispatchAction') in event handlers, we should find
          // lastHovered again here. Otherwise 'mouseout' can not be triggered normally.
          // See #6198.
          if (lastHoveredTarget && !lastHoveredTarget.__zr) {
              lastHovered = this.findHover(lastHovered.x, lastHovered.y);
              lastHoveredTarget = lastHovered.target;
          }

          var hovered = this._hovered = this.findHover(x, y);
          var hoveredTarget = hovered.target;

          var proxy = this.proxy;
          proxy.setCursor && proxy.setCursor(hoveredTarget ? hoveredTarget.cursor : 'default');

          // Mouse out on previous hovered element
          if (lastHoveredTarget && hoveredTarget !== lastHoveredTarget) {
              this.dispatchToElement(lastHovered, 'mouseout', event);
          }

          // Mouse moving on one element
          this.dispatchToElement(hovered, 'mousemove', event);

          // Mouse over on a new element
          if (hoveredTarget && hoveredTarget !== lastHoveredTarget) {
              this.dispatchToElement(hovered, 'mouseover', event);
          }
      },

      mouseout: function (event) {
          this.dispatchToElement(this._hovered, 'mouseout', event);

          // There might be some doms created by upper layer application
          // at the same level of painter.getViewportRoot() (e.g., tooltip
          // dom created by echarts), where 'globalout' event should not
          // be triggered when mouse enters these doms. (But 'mouseout'
          // should be triggered at the original hovered element as usual).
          var element = event.toElement || event.relatedTarget;
          var innerDom;
          do {
              element = element && element.parentNode;
          }
          while (element && element.nodeType !== 9 && !(
              innerDom = element === this.painterRoot
          ));

          !innerDom && this.trigger('globalout', {event: event});
      },

      /**
       * Resize
       */
      resize: function (event) {
          this._hovered = {};
      },

      /**
       * Dispatch event
       * @param {string} eventName
       * @param {event=} eventArgs
       */
      dispatch: function (eventName, eventArgs) {
          var handler = this[eventName];
          handler && handler.call(this, eventArgs);
      },

      /**
       * Dispose
       */
      dispose: function () {

          this.proxy.dispose();

          this.storage =
          this.proxy =
          this.painter = null;
      },

      /**
       * 设置默认的cursor style
       * @param {string} [cursorStyle='default'] 例如 crosshair
       */
      setCursorStyle: function (cursorStyle) {
          var proxy = this.proxy;
          proxy.setCursor && proxy.setCursor(cursorStyle);
      },

      /**
       * 事件分发代理
       *
       * @private
       * @param {Object} targetInfo {target, topTarget} 目标图形元素
       * @param {string} eventName 事件名称
       * @param {Object} event 事件对象
       */
      dispatchToElement: function (targetInfo, eventName, event) {
          targetInfo = targetInfo || {};
          var el = targetInfo.target;
          if (el && el.silent) {
              return;
          }
          var eventHandler = 'on' + eventName;
          var eventPacket = makeEventPacket(eventName, targetInfo, event);

          while (el) {
              el[eventHandler]
                  && (eventPacket.cancelBubble = el[eventHandler].call(el, eventPacket));

              el.trigger(eventName, eventPacket);

              el = el.parent;

              if (eventPacket.cancelBubble) {
                  break;
              }
          }

          if (!eventPacket.cancelBubble) {
              // 冒泡到顶级 zrender 对象
              this.trigger(eventName, eventPacket);
              // 分发事件到用户自定义层
              // 用户有可能在全局 click 事件中 dispose，所以需要判断下 painter 是否存在
              this.painter && this.painter.eachOtherLayer(function (layer) {
                  if (typeof (layer[eventHandler]) === 'function') {
                      layer[eventHandler].call(layer, eventPacket);
                  }
                  if (layer.trigger) {
                      layer.trigger(eventName, eventPacket);
                  }
              });
          }
      },

      /**
       * @private
       * @param {number} x
       * @param {number} y
       * @param {module:zrender/graphic/Displayable} exclude
       * @return {model:zrender/Element}
       * @method
       */
      findHover: function (x, y, exclude) {
          var list = this.storage.getDisplayList();
          var out = {x: x, y: y};

          for (var i = list.length - 1; i >= 0; i--) {
              var hoverCheckResult;
              if (list[i] !== exclude
                  // getDisplayList may include ignored item in VML mode
                  && !list[i].ignore
                  && (hoverCheckResult = isHover(list[i], x, y))
              ) {
                  !out.topTarget && (out.topTarget = list[i]);
                  if (hoverCheckResult !== SILENT) {
                      out.target = list[i];
                      break;
                  }
              }
          }

          return out;
      },

      processGesture: function (event, stage) {
          if (!this._gestureMgr) {
              this._gestureMgr = new GestureMgr();
          }
          var gestureMgr = this._gestureMgr;

          stage === 'start' && gestureMgr.clear();

          var gestureInfo = gestureMgr.recognize(
              event,
              this.findHover(event.zrX, event.zrY, null).target,
              this.proxy.dom
          );

          stage === 'end' && gestureMgr.clear();

          // Do not do any preventDefault here. Upper application do that if necessary.
          if (gestureInfo) {
              var type = gestureInfo.type;
              event.gestureEvent = type;

              this.dispatchToElement({target: gestureInfo.target}, type, gestureInfo.event);
          }
      }
  };

  // Common handlers
  each(['click', 'mousedown', 'mouseup', 'mousewheel', 'dblclick', 'contextmenu'], function (name) {
      Handler.prototype[name] = function (event) {
          // Find hover again to avoid click event is dispatched manually. Or click is triggered without mouseover
          var hovered = this.findHover(event.zrX, event.zrY);
          var hoveredTarget = hovered.target;

          if (name === 'mousedown') {
              this._downEl = hoveredTarget;
              this._downPoint = [event.zrX, event.zrY];
              // In case click triggered before mouseup
              this._upEl = hoveredTarget;
          }
          else if (name === 'mouseup') {
              this._upEl = hoveredTarget;
          }
          else if (name === 'click') {
              if (this._downEl !== this._upEl
                  // Original click event is triggered on the whole canvas element,
                  // including the case that `mousedown` - `mousemove` - `mouseup`,
                  // which should be filtered, otherwise it will bring trouble to
                  // pan and zoom.
                  || !this._downPoint
                  // Arbitrary value
                  || dist(this._downPoint, [event.zrX, event.zrY]) > 4
              ) {
                  return;
              }
              this._downPoint = null;
          }

          this.dispatchToElement(hovered, name, event);
      };
  });

  function isHover(displayable, x, y) {
      if (displayable[displayable.rectHover ? 'rectContain' : 'contain'](x, y)) {
          var el = displayable;
          var isSilent;
          while (el) {
              // If clipped by ancestor.
              // FIXME: If clipPath has neither stroke nor fill,
              // el.clipPath.contain(x, y) will always return false.
              if (el.clipPath && !el.clipPath.contain(x, y)) {
                  return false;
              }
              if (el.silent) {
                  isSilent = true;
              }
              el = el.parent;
          }
          return isSilent ? SILENT : true;
      }

      return false;
  }

  mixin(Handler, Eventful);
  mixin(Handler, Draggable);

  /**
   * Group是一个容器，可以插入子节点，Group的变换也会被应用到子节点上
   * @module zrender/graphic/Group
   * @example
   *     var Group = require('zrender/container/Group');
   *     var Circle = require('zrender/graphic/shape/Circle');
   *     var g = new Group();
   *     g.position[0] = 100;
   *     g.position[1] = 100;
   *     g.add(new Circle({
   *         style: {
   *             x: 100,
   *             y: 100,
   *             r: 20,
   *         }
   *     }));
   *     zr.add(g);
   */

  /**
   * @alias module:zrender/graphic/Group
   * @constructor
   * @extends module:zrender/mixin/Transformable
   * @extends module:zrender/mixin/Eventful
   */
  var Group = function (opts) {

      opts = opts || {};

      Element.call(this, opts);

      for (var key in opts) {
          if (opts.hasOwnProperty(key)) {
              this[key] = opts[key];
          }
      }

      this._children = [];

      this.__storage = null;

      this.__dirty = true;
  };

  Group.prototype = {

      constructor: Group,

      isGroup: true,

      /**
       * @type {string}
       */
      type: 'group',

      /**
       * 所有子孙元素是否响应鼠标事件
       * @name module:/zrender/container/Group#silent
       * @type {boolean}
       * @default false
       */
      silent: false,

      /**
       * @return {Array.<module:zrender/Element>}
       */
      children: function () {
          return this._children.slice();
      },

      /**
       * 获取指定 index 的儿子节点
       * @param  {number} idx
       * @return {module:zrender/Element}
       */
      childAt: function (idx) {
          return this._children[idx];
      },

      /**
       * 获取指定名字的儿子节点
       * @param  {string} name
       * @return {module:zrender/Element}
       */
      childOfName: function (name) {
          var children = this._children;
          for (var i = 0; i < children.length; i++) {
              if (children[i].name === name) {
                  return children[i];
              }
              }
      },

      /**
       * @return {number}
       */
      childCount: function () {
          return this._children.length;
      },

      /**
       * 添加子节点到最后
       * @param {module:zrender/Element} child
       */
      add: function (child) {
          if (child && child !== this && child.parent !== this) {

              this._children.push(child);

              this._doAdd(child);
          }

          return this;
      },

      /**
       * 添加子节点在 nextSibling 之前
       * @param {module:zrender/Element} child
       * @param {module:zrender/Element} nextSibling
       */
      addBefore: function (child, nextSibling) {
          if (child && child !== this && child.parent !== this
              && nextSibling && nextSibling.parent === this) {

              var children = this._children;
              var idx = children.indexOf(nextSibling);

              if (idx >= 0) {
                  children.splice(idx, 0, child);
                  this._doAdd(child);
              }
          }

          return this;
      },

      _doAdd: function (child) {
          if (child.parent) {
              child.parent.remove(child);
          }

          child.parent = this;

          var storage = this.__storage;
          var zr = this.__zr;
          if (storage && storage !== child.__storage) {

              storage.addToStorage(child);

              if (child instanceof Group) {
                  child.addChildrenToStorage(storage);
              }
          }

          zr && zr.refresh();
      },

      /**
       * 移除子节点
       * @param {module:zrender/Element} child
       */
      remove: function (child) {
          var zr = this.__zr;
          var storage = this.__storage;
          var children = this._children;

          var idx = indexOf(children, child);
          if (idx < 0) {
              return this;
          }
          children.splice(idx, 1);

          child.parent = null;

          if (storage) {

              storage.delFromStorage(child);

              if (child instanceof Group) {
                  child.delChildrenFromStorage(storage);
              }
          }

          zr && zr.refresh();

          return this;
      },

      /**
       * 移除所有子节点
       */
      removeAll: function () {
          var children = this._children;
          var storage = this.__storage;
          var child;
          var i;
          for (i = 0; i < children.length; i++) {
              child = children[i];
              if (storage) {
                  storage.delFromStorage(child);
                  if (child instanceof Group) {
                      child.delChildrenFromStorage(storage);
                  }
              }
              child.parent = null;
          }
          children.length = 0;

          return this;
      },

      /**
       * 遍历所有子节点
       * @param  {Function} cb
       * @param  {}   context
       */
      eachChild: function (cb, context) {
          var children = this._children;
          for (var i = 0; i < children.length; i++) {
              var child = children[i];
              cb.call(context, child, i);
          }
          return this;
      },

      /**
       * 深度优先遍历所有子孙节点
       * @param  {Function} cb
       * @param  {}   context
       */
      traverse: function (cb, context) {
          for (var i = 0; i < this._children.length; i++) {
              var child = this._children[i];
              cb.call(context, child);

              if (child.type === 'group') {
                  child.traverse(cb, context);
              }
          }
          return this;
      },

      addChildrenToStorage: function (storage) {
          for (var i = 0; i < this._children.length; i++) {
              var child = this._children[i];
              storage.addToStorage(child);
              if (child instanceof Group) {
                  child.addChildrenToStorage(storage);
              }
          }
      },

      delChildrenFromStorage: function (storage) {
          for (var i = 0; i < this._children.length; i++) {
              var child = this._children[i];
              storage.delFromStorage(child);
              if (child instanceof Group) {
                  child.delChildrenFromStorage(storage);
              }
          }
      },

      dirty: function () {
          this.__dirty = true;
          this.__zr && this.__zr.refresh();
          return this;
      },

      /**
       * @return {module:zrender/core/BoundingRect}
       */
      getBoundingRect: function (includeChildren) {
          // TODO Caching
          var rect = null;
          var tmpRect = new BoundingRect(0, 0, 0, 0);
          var children = includeChildren || this._children;
          var tmpMat = [];

          for (var i = 0; i < children.length; i++) {
              var child = children[i];
              if (child.ignore || child.invisible) {
                  continue;
              }

              var childRect = child.getBoundingRect();
              var transform = child.getLocalTransform(tmpMat);
              // TODO
              // The boundingRect cacluated by transforming original
              // rect may be bigger than the actual bundingRect when rotation
              // is used. (Consider a circle rotated aginst its center, where
              // the actual boundingRect should be the same as that not be
              // rotated.) But we can not find better approach to calculate
              // actual boundingRect yet, considering performance.
              if (transform) {
                  tmpRect.copy(childRect);
                  tmpRect.applyTransform(transform);
                  rect = rect || tmpRect.clone();
                  rect.union(tmpRect);
              }
              else {
                  rect = rect || childRect.clone();
                  rect.union(childRect);
              }
          }
          return rect || tmpRect;
      }
  };

  inherits(Group, Element);

  // https://github.com/mziccard/node-timsort
  var DEFAULT_MIN_MERGE = 32;

  var DEFAULT_MIN_GALLOPING = 7;

  function minRunLength(n) {
      var r = 0;

      while (n >= DEFAULT_MIN_MERGE) {
          r |= n & 1;
          n >>= 1;
      }

      return n + r;
  }

  function makeAscendingRun(array, lo, hi, compare) {
      var runHi = lo + 1;

      if (runHi === hi) {
          return 1;
      }

      if (compare(array[runHi++], array[lo]) < 0) {
          while (runHi < hi && compare(array[runHi], array[runHi - 1]) < 0) {
              runHi++;
          }

          reverseRun(array, lo, runHi);
      }
      else {
          while (runHi < hi && compare(array[runHi], array[runHi - 1]) >= 0) {
              runHi++;
          }
      }

      return runHi - lo;
  }

  function reverseRun(array, lo, hi) {
      hi--;

      while (lo < hi) {
          var t = array[lo];
          array[lo++] = array[hi];
          array[hi--] = t;
      }
  }

  function binaryInsertionSort(array, lo, hi, start, compare) {
      if (start === lo) {
          start++;
      }

      for (; start < hi; start++) {
          var pivot = array[start];

          var left = lo;
          var right = start;
          var mid;

          while (left < right) {
              mid = left + right >>> 1;

              if (compare(pivot, array[mid]) < 0) {
                  right = mid;
              }
              else {
                  left = mid + 1;
              }
          }

          var n = start - left;

          switch (n) {
              case 3:
                  array[left + 3] = array[left + 2];

              case 2:
                  array[left + 2] = array[left + 1];

              case 1:
                  array[left + 1] = array[left];
                  break;
              default:
                  while (n > 0) {
                      array[left + n] = array[left + n - 1];
                      n--;
                  }
          }

          array[left] = pivot;
      }
  }

  function gallopLeft(value, array, start, length, hint, compare) {
      var lastOffset = 0;
      var maxOffset = 0;
      var offset = 1;

      if (compare(value, array[start + hint]) > 0) {
          maxOffset = length - hint;

          while (offset < maxOffset && compare(value, array[start + hint + offset]) > 0) {
              lastOffset = offset;
              offset = (offset << 1) + 1;

              if (offset <= 0) {
                  offset = maxOffset;
              }
          }

          if (offset > maxOffset) {
              offset = maxOffset;
          }

          lastOffset += hint;
          offset += hint;
      }
      else {
          maxOffset = hint + 1;
          while (offset < maxOffset && compare(value, array[start + hint - offset]) <= 0) {
              lastOffset = offset;
              offset = (offset << 1) + 1;

              if (offset <= 0) {
                  offset = maxOffset;
              }
          }
          if (offset > maxOffset) {
              offset = maxOffset;
          }

          var tmp = lastOffset;
          lastOffset = hint - offset;
          offset = hint - tmp;
      }

      lastOffset++;
      while (lastOffset < offset) {
          var m = lastOffset + (offset - lastOffset >>> 1);

          if (compare(value, array[start + m]) > 0) {
              lastOffset = m + 1;
          }
          else {
              offset = m;
          }
      }
      return offset;
  }

  function gallopRight(value, array, start, length, hint, compare) {
      var lastOffset = 0;
      var maxOffset = 0;
      var offset = 1;

      if (compare(value, array[start + hint]) < 0) {
          maxOffset = hint + 1;

          while (offset < maxOffset && compare(value, array[start + hint - offset]) < 0) {
              lastOffset = offset;
              offset = (offset << 1) + 1;

              if (offset <= 0) {
                  offset = maxOffset;
              }
          }

          if (offset > maxOffset) {
              offset = maxOffset;
          }

          var tmp = lastOffset;
          lastOffset = hint - offset;
          offset = hint - tmp;
      }
      else {
          maxOffset = length - hint;

          while (offset < maxOffset && compare(value, array[start + hint + offset]) >= 0) {
              lastOffset = offset;
              offset = (offset << 1) + 1;

              if (offset <= 0) {
                  offset = maxOffset;
              }
          }

          if (offset > maxOffset) {
              offset = maxOffset;
          }

          lastOffset += hint;
          offset += hint;
      }

      lastOffset++;

      while (lastOffset < offset) {
          var m = lastOffset + (offset - lastOffset >>> 1);

          if (compare(value, array[start + m]) < 0) {
              offset = m;
          }
          else {
              lastOffset = m + 1;
          }
      }

      return offset;
  }

  function TimSort(array, compare) {
      var minGallop = DEFAULT_MIN_GALLOPING;
      var length = 0;
      var runStart;
      var runLength;
      var stackSize = 0;

      length = array.length;

      var tmp = [];

      runStart = [];
      runLength = [];

      function pushRun(_runStart, _runLength) {
          runStart[stackSize] = _runStart;
          runLength[stackSize] = _runLength;
          stackSize += 1;
      }

      function mergeRuns() {
          while (stackSize > 1) {
              var n = stackSize - 2;

              if (n >= 1 && runLength[n - 1] <= runLength[n] + runLength[n + 1] || n >= 2 && runLength[n - 2] <= runLength[n] + runLength[n - 1]) {
                  if (runLength[n - 1] < runLength[n + 1]) {
                      n--;
                  }
              }
              else if (runLength[n] > runLength[n + 1]) {
                  break;
              }
              mergeAt(n);
          }
      }

      function forceMergeRuns() {
          while (stackSize > 1) {
              var n = stackSize - 2;

              if (n > 0 && runLength[n - 1] < runLength[n + 1]) {
                  n--;
              }

              mergeAt(n);
          }
      }

      function mergeAt(i) {
          var start1 = runStart[i];
          var length1 = runLength[i];
          var start2 = runStart[i + 1];
          var length2 = runLength[i + 1];

          runLength[i] = length1 + length2;

          if (i === stackSize - 3) {
              runStart[i + 1] = runStart[i + 2];
              runLength[i + 1] = runLength[i + 2];
          }

          stackSize--;

          var k = gallopRight(array[start2], array, start1, length1, 0, compare);
          start1 += k;
          length1 -= k;

          if (length1 === 0) {
              return;
          }

          length2 = gallopLeft(array[start1 + length1 - 1], array, start2, length2, length2 - 1, compare);

          if (length2 === 0) {
              return;
          }

          if (length1 <= length2) {
              mergeLow(start1, length1, start2, length2);
          }
          else {
              mergeHigh(start1, length1, start2, length2);
          }
      }

      function mergeLow(start1, length1, start2, length2) {
          var i = 0;

          for (i = 0; i < length1; i++) {
              tmp[i] = array[start1 + i];
          }

          var cursor1 = 0;
          var cursor2 = start2;
          var dest = start1;

          array[dest++] = array[cursor2++];

          if (--length2 === 0) {
              for (i = 0; i < length1; i++) {
                  array[dest + i] = tmp[cursor1 + i];
              }
              return;
          }

          if (length1 === 1) {
              for (i = 0; i < length2; i++) {
                  array[dest + i] = array[cursor2 + i];
              }
              array[dest + length2] = tmp[cursor1];
              return;
          }

          var _minGallop = minGallop;
          var count1, count2, exit;

          while (1) {
              count1 = 0;
              count2 = 0;
              exit = false;

              do {
                  if (compare(array[cursor2], tmp[cursor1]) < 0) {
                      array[dest++] = array[cursor2++];
                      count2++;
                      count1 = 0;

                      if (--length2 === 0) {
                          exit = true;
                          break;
                      }
                  }
                  else {
                      array[dest++] = tmp[cursor1++];
                      count1++;
                      count2 = 0;
                      if (--length1 === 1) {
                          exit = true;
                          break;
                      }
                  }
              } while ((count1 | count2) < _minGallop);

              if (exit) {
                  break;
              }

              do {
                  count1 = gallopRight(array[cursor2], tmp, cursor1, length1, 0, compare);

                  if (count1 !== 0) {
                      for (i = 0; i < count1; i++) {
                          array[dest + i] = tmp[cursor1 + i];
                      }

                      dest += count1;
                      cursor1 += count1;
                      length1 -= count1;
                      if (length1 <= 1) {
                          exit = true;
                          break;
                      }
                  }

                  array[dest++] = array[cursor2++];

                  if (--length2 === 0) {
                      exit = true;
                      break;
                  }

                  count2 = gallopLeft(tmp[cursor1], array, cursor2, length2, 0, compare);

                  if (count2 !== 0) {
                      for (i = 0; i < count2; i++) {
                          array[dest + i] = array[cursor2 + i];
                      }

                      dest += count2;
                      cursor2 += count2;
                      length2 -= count2;

                      if (length2 === 0) {
                          exit = true;
                          break;
                      }
                  }
                  array[dest++] = tmp[cursor1++];

                  if (--length1 === 1) {
                      exit = true;
                      break;
                  }

                  _minGallop--;
              } while (count1 >= DEFAULT_MIN_GALLOPING || count2 >= DEFAULT_MIN_GALLOPING);

              if (exit) {
                  break;
              }

              if (_minGallop < 0) {
                  _minGallop = 0;
              }

              _minGallop += 2;
          }

          minGallop = _minGallop;

          minGallop < 1 && (minGallop = 1);

          if (length1 === 1) {
              for (i = 0; i < length2; i++) {
                  array[dest + i] = array[cursor2 + i];
              }
              array[dest + length2] = tmp[cursor1];
          }
          else if (length1 === 0) {
              throw new Error();
              // throw new Error('mergeLow preconditions were not respected');
          }
          else {
              for (i = 0; i < length1; i++) {
                  array[dest + i] = tmp[cursor1 + i];
              }
          }
      }

      function mergeHigh(start1, length1, start2, length2) {
          var i = 0;

          for (i = 0; i < length2; i++) {
              tmp[i] = array[start2 + i];
          }

          var cursor1 = start1 + length1 - 1;
          var cursor2 = length2 - 1;
          var dest = start2 + length2 - 1;
          var customCursor = 0;
          var customDest = 0;

          array[dest--] = array[cursor1--];

          if (--length1 === 0) {
              customCursor = dest - (length2 - 1);

              for (i = 0; i < length2; i++) {
                  array[customCursor + i] = tmp[i];
              }

              return;
          }

          if (length2 === 1) {
              dest -= length1;
              cursor1 -= length1;
              customDest = dest + 1;
              customCursor = cursor1 + 1;

              for (i = length1 - 1; i >= 0; i--) {
                  array[customDest + i] = array[customCursor + i];
              }

              array[dest] = tmp[cursor2];
              return;
          }

          var _minGallop = minGallop;

          while (true) {
              var count1 = 0;
              var count2 = 0;
              var exit = false;

              do {
                  if (compare(tmp[cursor2], array[cursor1]) < 0) {
                      array[dest--] = array[cursor1--];
                      count1++;
                      count2 = 0;
                      if (--length1 === 0) {
                          exit = true;
                          break;
                      }
                  }
                  else {
                      array[dest--] = tmp[cursor2--];
                      count2++;
                      count1 = 0;
                      if (--length2 === 1) {
                          exit = true;
                          break;
                      }
                  }
              } while ((count1 | count2) < _minGallop);

              if (exit) {
                  break;
              }

              do {
                  count1 = length1 - gallopRight(tmp[cursor2], array, start1, length1, length1 - 1, compare);

                  if (count1 !== 0) {
                      dest -= count1;
                      cursor1 -= count1;
                      length1 -= count1;
                      customDest = dest + 1;
                      customCursor = cursor1 + 1;

                      for (i = count1 - 1; i >= 0; i--) {
                          array[customDest + i] = array[customCursor + i];
                      }

                      if (length1 === 0) {
                          exit = true;
                          break;
                      }
                  }

                  array[dest--] = tmp[cursor2--];

                  if (--length2 === 1) {
                      exit = true;
                      break;
                  }

                  count2 = length2 - gallopLeft(array[cursor1], tmp, 0, length2, length2 - 1, compare);

                  if (count2 !== 0) {
                      dest -= count2;
                      cursor2 -= count2;
                      length2 -= count2;
                      customDest = dest + 1;
                      customCursor = cursor2 + 1;

                      for (i = 0; i < count2; i++) {
                          array[customDest + i] = tmp[customCursor + i];
                      }

                      if (length2 <= 1) {
                          exit = true;
                          break;
                      }
                  }

                  array[dest--] = array[cursor1--];

                  if (--length1 === 0) {
                      exit = true;
                      break;
                  }

                  _minGallop--;
              } while (count1 >= DEFAULT_MIN_GALLOPING || count2 >= DEFAULT_MIN_GALLOPING);

              if (exit) {
                  break;
              }

              if (_minGallop < 0) {
                  _minGallop = 0;
              }

              _minGallop += 2;
          }

          minGallop = _minGallop;

          if (minGallop < 1) {
              minGallop = 1;
          }

          if (length2 === 1) {
              dest -= length1;
              cursor1 -= length1;
              customDest = dest + 1;
              customCursor = cursor1 + 1;

              for (i = length1 - 1; i >= 0; i--) {
                  array[customDest + i] = array[customCursor + i];
              }

              array[dest] = tmp[cursor2];
          }
          else if (length2 === 0) {
              throw new Error();
              // throw new Error('mergeHigh preconditions were not respected');
          }
          else {
              customCursor = dest - (length2 - 1);
              for (i = 0; i < length2; i++) {
                  array[customCursor + i] = tmp[i];
              }
          }
      }

      this.mergeRuns = mergeRuns;
      this.forceMergeRuns = forceMergeRuns;
      this.pushRun = pushRun;
  }

  function sort(array, compare, lo, hi) {
      if (!lo) {
          lo = 0;
      }
      if (!hi) {
          hi = array.length;
      }

      var remaining = hi - lo;

      if (remaining < 2) {
          return;
      }

      var runLength = 0;

      if (remaining < DEFAULT_MIN_MERGE) {
          runLength = makeAscendingRun(array, lo, hi, compare);
          binaryInsertionSort(array, lo, hi, lo + runLength, compare);
          return;
      }

      var ts = new TimSort(array, compare);

      var minRun = minRunLength(remaining);

      do {
          runLength = makeAscendingRun(array, lo, hi, compare);
          if (runLength < minRun) {
              var force = remaining;
              if (force > minRun) {
                  force = minRun;
              }

              binaryInsertionSort(array, lo, lo + force, lo + runLength, compare);
              runLength = force;
          }

          ts.pushRun(lo, runLength);
          ts.mergeRuns();

          remaining -= runLength;
          lo += runLength;
      } while (remaining !== 0);

      ts.forceMergeRuns();
  }

  function shapeCompareFunc(a, b) {
      if (a.zlevel === b.zlevel) {
          if (a.z === b.z) {
              // if (a.z2 === b.z2) {
              //     // FIXME Slow has renderidx compare
              //     // http://stackoverflow.com/questions/20883421/sorting-in-javascript-should-every-compare-function-have-a-return-0-statement
              //     // https://github.com/v8/v8/blob/47cce544a31ed5577ffe2963f67acb4144ee0232/src/js/array.js#L1012
              //     return a.__renderidx - b.__renderidx;
              // }
              return a.z2 - b.z2;
          }
          return a.z - b.z;
      }
      return a.zlevel - b.zlevel;
  }
  /**
   * 内容仓库 (M)
   * @alias module:zrender/Storage
   * @constructor
   */
  var Storage = function () { // jshint ignore:line
      this._roots = [];

      this._displayList = [];

      this._displayListLen = 0;
  };

  Storage.prototype = {

      constructor: Storage,

      /**
       * @param  {Function} cb
       *
       */
      traverse: function (cb, context) {
          for (var i = 0; i < this._roots.length; i++) {
              this._roots[i].traverse(cb, context);
          }
      },

      /**
       * 返回所有图形的绘制队列
       * @param {boolean} [update=false] 是否在返回前更新该数组
       * @param {boolean} [includeIgnore=false] 是否包含 ignore 的数组, 在 update 为 true 的时候有效
       *
       * 详见{@link module:zrender/graphic/Displayable.prototype.updateDisplayList}
       * @return {Array.<module:zrender/graphic/Displayable>}
       */
      getDisplayList: function (update, includeIgnore) {
          includeIgnore = includeIgnore || false;
          if (update) {
              this.updateDisplayList(includeIgnore);
          }
          return this._displayList;
      },

      /**
       * 更新图形的绘制队列。
       * 每次绘制前都会调用，该方法会先深度优先遍历整个树，更新所有Group和Shape的变换并且把所有可见的Shape保存到数组中，
       * 最后根据绘制的优先级（zlevel > z > 插入顺序）排序得到绘制队列
       * @param {boolean} [includeIgnore=false] 是否包含 ignore 的数组
       */
      updateDisplayList: function (includeIgnore) {
          this._displayListLen = 0;

          var roots = this._roots;
          var displayList = this._displayList;
          for (var i = 0, len = roots.length; i < len; i++) {
              this._updateAndAddDisplayable(roots[i], null, includeIgnore);
          }

          displayList.length = this._displayListLen;

          env$1.canvasSupported && sort(displayList, shapeCompareFunc);
      },

      _updateAndAddDisplayable: function (el, clipPaths, includeIgnore) {

          if (el.ignore && !includeIgnore) {
              return;
          }

          el.beforeUpdate();

          if (el.__dirty) {

              el.update();

          }

          el.afterUpdate();

          var userSetClipPath = el.clipPath;
          if (userSetClipPath) {

              // FIXME 效率影响
              if (clipPaths) {
                  clipPaths = clipPaths.slice();
              }
              else {
                  clipPaths = [];
              }

              var currentClipPath = userSetClipPath;
              var parentClipPath = el;
              // Recursively add clip path
              while (currentClipPath) {
                  // clipPath 的变换是基于使用这个 clipPath 的元素
                  currentClipPath.parent = parentClipPath;
                  currentClipPath.updateTransform();

                  clipPaths.push(currentClipPath);

                  parentClipPath = currentClipPath;
                  currentClipPath = currentClipPath.clipPath;
              }
          }

          if (el.isGroup) {
              var children = el._children;

              for (var i = 0; i < children.length; i++) {
                  var child = children[i];

                  // Force to mark as dirty if group is dirty
                  // FIXME __dirtyPath ?
                  if (el.__dirty) {
                      child.__dirty = true;
                  }

                  this._updateAndAddDisplayable(child, clipPaths, includeIgnore);
              }

              // Mark group clean here
              el.__dirty = false;

          }
          else {
              el.__clipPaths = clipPaths;

              this._displayList[this._displayListLen++] = el;
          }
      },

      /**
       * 添加图形(Shape)或者组(Group)到根节点
       * @param {module:zrender/Element} el
       */
      addRoot: function (el) {
          if (el.__storage === this) {
              return;
          }

          if (el instanceof Group) {
              el.addChildrenToStorage(this);
          }

          this.addToStorage(el);
          this._roots.push(el);
      },

      /**
       * 删除指定的图形(Shape)或者组(Group)
       * @param {string|Array.<string>} [el] 如果为空清空整个Storage
       */
      delRoot: function (el) {
          if (el == null) {
              // 不指定el清空
              for (var i = 0; i < this._roots.length; i++) {
                  var root = this._roots[i];
                  if (root instanceof Group) {
                      root.delChildrenFromStorage(this);
                  }
              }

              this._roots = [];
              this._displayList = [];
              this._displayListLen = 0;

              return;
          }

          if (el instanceof Array) {
              for (var i = 0, l = el.length; i < l; i++) {
                  this.delRoot(el[i]);
              }
              return;
          }


          var idx = indexOf(this._roots, el);
          if (idx >= 0) {
              this.delFromStorage(el);
              this._roots.splice(idx, 1);
              if (el instanceof Group) {
                  el.delChildrenFromStorage(this);
              }
          }
      },

      addToStorage: function (el) {
          if (el) {
              el.__storage = this;
              el.dirty(false);
          }
          return this;
      },

      delFromStorage: function (el) {
          if (el) {
              el.__storage = null;
          }

          return this;
      },

      /**
       * 清空并且释放Storage
       */
      dispose: function () {
          this._renderList =
          this._roots = null;
      },

      displayableSortFunc: shapeCompareFunc
  };

  var Pattern = function (image, repeat) {
      // Should do nothing more in this constructor. Because gradient can be
      // declard by `color: {image: ...}`, where this constructor will not be called.

      this.image = image;
      this.repeat = repeat;

      // Can be cloned
      this.type = 'pattern';
  };

  Pattern.prototype.getCanvasPattern = function (ctx) {
      return ctx.createPattern(this.image, this.repeat || 'repeat');
  };

  /**
   * @module zrender/Layer
   * @author pissang(https://www.github.com/pissang)
   */

  function returnFalse() {
      return false;
  }

  /**
   * 创建dom
   *
   * @inner
   * @param {string} id dom id 待用
   * @param {Painter} painter painter instance
   * @param {number} number
   */
  function createDom(id, painter, dpr) {
      var newDom = createCanvas();
      var width = painter.getWidth();
      var height = painter.getHeight();

      var newDomStyle = newDom.style;
      if (newDomStyle) {  // In node or some other non-browser environment
          newDomStyle.position = 'absolute';
          newDomStyle.left = 0;
          newDomStyle.top = 0;
          newDomStyle.width = width + 'px';
          newDomStyle.height = height + 'px';

          newDom.setAttribute('data-zr-dom-id', id);
      }

      newDom.width = width * dpr;
      newDom.height = height * dpr;

      return newDom;
  }

  /**
   * @alias module:zrender/Layer
   * @constructor
   * @extends module:zrender/mixin/Transformable
   * @param {string} id
   * @param {module:zrender/Painter} painter
   * @param {number} [dpr]
   */
  var Layer = function (id, painter, dpr) {
      var dom;
      dpr = dpr || devicePixelRatio;
      if (typeof id === 'string') {
          dom = createDom(id, painter, dpr);
      }
      // Not using isDom because in node it will return false
      else if (isObject(id)) {
          dom = id;
          id = dom.id;
      }
      this.id = id;
      this.dom = dom;

      var domStyle = dom.style;
      if (domStyle) { // Not in node
          dom.onselectstart = returnFalse; // 避免页面选中的尴尬
          domStyle['-webkit-user-select'] = 'none';
          domStyle['user-select'] = 'none';
          domStyle['-webkit-touch-callout'] = 'none';
          domStyle['-webkit-tap-highlight-color'] = 'rgba(0,0,0,0)';
          domStyle['padding'] = 0;
          domStyle['margin'] = 0;
          domStyle['border-width'] = 0;
      }

      this.domBack = null;
      this.ctxBack = null;

      this.painter = painter;

      this.config = null;

      // Configs
      /**
       * 每次清空画布的颜色
       * @type {string}
       * @default 0
       */
      this.clearColor = 0;
      /**
       * 是否开启动态模糊
       * @type {boolean}
       * @default false
       */
      this.motionBlur = false;
      /**
       * 在开启动态模糊的时候使用，与上一帧混合的alpha值，值越大尾迹越明显
       * @type {number}
       * @default 0.7
       */
      this.lastFrameAlpha = 0.7;

      /**
       * Layer dpr
       * @type {number}
       */
      this.dpr = dpr;
  };

  Layer.prototype = {

      constructor: Layer,

      __dirty: true,

      __used: false,

      __drawIndex: 0,
      __startIndex: 0,
      __endIndex: 0,

      incremental: false,

      getElementCount: function () {
          return this.__endIndex - this.__startIndex;
      },

      initContext: function () {
          this.ctx = this.dom.getContext('2d');
          this.ctx.dpr = this.dpr;
      },

      createBackBuffer: function () {
          var dpr = this.dpr;

          this.domBack = createDom('back-' + this.id, this.painter, dpr);
          this.ctxBack = this.domBack.getContext('2d');

          if (dpr !== 1) {
              this.ctxBack.scale(dpr, dpr);
          }
      },

      /**
       * @param  {number} width
       * @param  {number} height
       */
      resize: function (width, height) {
          var dpr = this.dpr;

          var dom = this.dom;
          var domStyle = dom.style;
          var domBack = this.domBack;

          if (domStyle) {
              domStyle.width = width + 'px';
              domStyle.height = height + 'px';
          }

          dom.width = width * dpr;
          dom.height = height * dpr;

          if (domBack) {
              domBack.width = width * dpr;
              domBack.height = height * dpr;

              if (dpr !== 1) {
                  this.ctxBack.scale(dpr, dpr);
              }
          }
      },

      /**
       * 清空该层画布
       * @param {boolean} [clearAll]=false Clear all with out motion blur
       * @param {Color} [clearColor]
       */
      clear: function (clearAll, clearColor) {
          var dom = this.dom;
          var ctx = this.ctx;
          var width = dom.width;
          var height = dom.height;

          var clearColor = clearColor || this.clearColor;
          var haveMotionBLur = this.motionBlur && !clearAll;
          var lastFrameAlpha = this.lastFrameAlpha;

          var dpr = this.dpr;

          if (haveMotionBLur) {
              if (!this.domBack) {
                  this.createBackBuffer();
              }

              this.ctxBack.globalCompositeOperation = 'copy';
              this.ctxBack.drawImage(
                  dom, 0, 0,
                  width / dpr,
                  height / dpr
              );
          }

          ctx.clearRect(0, 0, width, height);
          if (clearColor && clearColor !== 'transparent') {
              var clearColorGradientOrPattern;
              // Gradient
              if (clearColor.colorStops) {
                  // Cache canvas gradient
                  clearColorGradientOrPattern = clearColor.__canvasGradient || Style.getGradient(ctx, clearColor, {
                      x: 0,
                      y: 0,
                      width: width,
                      height: height
                  });

                  clearColor.__canvasGradient = clearColorGradientOrPattern;
              }
              // Pattern
              else if (clearColor.image) {
                  clearColorGradientOrPattern = Pattern.prototype.getCanvasPattern.call(clearColor, ctx);
              }
              ctx.save();
              ctx.fillStyle = clearColorGradientOrPattern || clearColor;
              ctx.fillRect(0, 0, width, height);
              ctx.restore();
          }

          if (haveMotionBLur) {
              var domBack = this.domBack;
              ctx.save();
              ctx.globalAlpha = lastFrameAlpha;
              ctx.drawImage(domBack, 0, 0, width, height);
              ctx.restore();
          }
      }
  };

  var requestAnimationFrame = (
      typeof window !== 'undefined'
      && (
          (window.requestAnimationFrame && window.requestAnimationFrame.bind(window))
          // https://github.com/ecomfe/zrender/issues/189#issuecomment-224919809
          || (window.msRequestAnimationFrame && window.msRequestAnimationFrame.bind(window))
          || window.mozRequestAnimationFrame
          || window.webkitRequestAnimationFrame
      )
  ) || function (func) {
      setTimeout(func, 16);
  };

  var HOVER_LAYER_ZLEVEL = 1e5;
  var CANVAS_ZLEVEL = 314159;

  var EL_AFTER_INCREMENTAL_INC = 0.01;
  var INCREMENTAL_INC = 0.001;

  function parseInt10(val) {
      return parseInt(val, 10);
  }

  function isLayerValid(layer) {
      if (!layer) {
          return false;
      }

      if (layer.__builtin__) {
          return true;
      }

      if (typeof (layer.resize) !== 'function'
          || typeof (layer.refresh) !== 'function'
      ) {
          return false;
      }

      return true;
  }

  var tmpRect$1 = new BoundingRect(0, 0, 0, 0);
  var viewRect = new BoundingRect(0, 0, 0, 0);
  function isDisplayableCulled(el, width, height) {
      tmpRect$1.copy(el.getBoundingRect());
      if (el.transform) {
          tmpRect$1.applyTransform(el.transform);
      }
      viewRect.width = width;
      viewRect.height = height;
      return !tmpRect$1.intersect(viewRect);
  }

  function isClipPathChanged(clipPaths, prevClipPaths) {
      if (clipPaths === prevClipPaths) { // Can both be null or undefined
          return false;
      }

      if (!clipPaths || !prevClipPaths || (clipPaths.length !== prevClipPaths.length)) {
          return true;
      }
      for (var i = 0; i < clipPaths.length; i++) {
          if (clipPaths[i] !== prevClipPaths[i]) {
              return true;
          }
      }
  }

  function doClip(clipPaths, ctx) {
      for (var i = 0; i < clipPaths.length; i++) {
          var clipPath = clipPaths[i];

          clipPath.setTransform(ctx);
          ctx.beginPath();
          clipPath.buildPath(ctx, clipPath.shape);
          ctx.clip();
          // Transform back
          clipPath.restoreTransform(ctx);
      }
  }

  function createRoot(width, height) {
      var domRoot = document.createElement('div');

      // domRoot.onselectstart = returnFalse; // 避免页面选中的尴尬
      domRoot.style.cssText = [
          'position:relative',
          'overflow:hidden',
          'width:' + width + 'px',
          'height:' + height + 'px',
          'padding:0',
          'margin:0',
          'border-width:0'
      ].join(';') + ';';

      return domRoot;
  }


  /**
   * @alias module:zrender/Painter
   * @constructor
   * @param {HTMLElement} root 绘图容器
   * @param {module:zrender/Storage} storage
   * @param {Object} opts
   */
  var Painter = function (root, storage, opts) {

      this.type = 'canvas';

      // In node environment using node-canvas
      var singleCanvas = !root.nodeName // In node ?
          || root.nodeName.toUpperCase() === 'CANVAS';

      this._opts = opts = extend({}, opts || {});

      /**
       * @type {number}
       */
      this.dpr = opts.devicePixelRatio || devicePixelRatio;
      /**
       * @type {boolean}
       * @private
       */
      this._singleCanvas = singleCanvas;
      /**
       * 绘图容器
       * @type {HTMLElement}
       */
      this.root = root;

      var rootStyle = root.style;

      if (rootStyle) {
          rootStyle['-webkit-tap-highlight-color'] = 'transparent';
          rootStyle['-webkit-user-select'] =
          rootStyle['user-select'] =
          rootStyle['-webkit-touch-callout'] = 'none';

          root.innerHTML = '';
      }

      /**
       * @type {module:zrender/Storage}
       */
      this.storage = storage;

      /**
       * @type {Array.<number>}
       * @private
       */
      var zlevelList = this._zlevelList = [];

      /**
       * @type {Object.<string, module:zrender/Layer>}
       * @private
       */
      var layers = this._layers = {};

      /**
       * @type {Object.<string, Object>}
       * @private
       */
      this._layerConfig = {};

      /**
       * zrender will do compositing when root is a canvas and have multiple zlevels.
       */
      this._needsManuallyCompositing = false;

      if (!singleCanvas) {
          this._width = this._getSize(0);
          this._height = this._getSize(1);

          var domRoot = this._domRoot = createRoot(
              this._width, this._height
          );
          root.appendChild(domRoot);
      }
      else {
          var width = root.width;
          var height = root.height;

          if (opts.width != null) {
              width = opts.width;
          }
          if (opts.height != null) {
              height = opts.height;
          }
          this.dpr = opts.devicePixelRatio || 1;

          // Use canvas width and height directly
          root.width = width * this.dpr;
          root.height = height * this.dpr;

          this._width = width;
          this._height = height;

          // Create layer if only one given canvas
          // Device can be specified to create a high dpi image.
          var mainLayer = new Layer(root, this, this.dpr);
          mainLayer.__builtin__ = true;
          mainLayer.initContext();
          // FIXME Use canvas width and height
          // mainLayer.resize(width, height);
          layers[CANVAS_ZLEVEL] = mainLayer;
          mainLayer.zlevel = CANVAS_ZLEVEL;
          // Not use common zlevel.
          zlevelList.push(CANVAS_ZLEVEL);

          this._domRoot = root;
      }

      /**
       * @type {module:zrender/Layer}
       * @private
       */
      this._hoverlayer = null;

      this._hoverElements = [];
  };

  Painter.prototype = {

      constructor: Painter,

      getType: function () {
          return 'canvas';
      },

      /**
       * If painter use a single canvas
       * @return {boolean}
       */
      isSingleCanvas: function () {
          return this._singleCanvas;
      },
      /**
       * @return {HTMLDivElement}
       */
      getViewportRoot: function () {
          return this._domRoot;
      },

      getViewportRootOffset: function () {
          var viewportRoot = this.getViewportRoot();
          if (viewportRoot) {
              return {
                  offsetLeft: viewportRoot.offsetLeft || 0,
                  offsetTop: viewportRoot.offsetTop || 0
              };
          }
      },

      /**
       * 刷新
       * @param {boolean} [paintAll=false] 强制绘制所有displayable
       */
      refresh: function (paintAll) {

          var list = this.storage.getDisplayList(true);

          var zlevelList = this._zlevelList;

          this._redrawId = Math.random();

          this._paintList(list, paintAll, this._redrawId);

          // Paint custum layers
          for (var i = 0; i < zlevelList.length; i++) {
              var z = zlevelList[i];
              var layer = this._layers[z];
              if (!layer.__builtin__ && layer.refresh) {
                  var clearColor = i === 0 ? this._backgroundColor : null;
                  layer.refresh(clearColor);
              }
          }

          this.refreshHover();

          return this;
      },

      addHover: function (el, hoverStyle) {
          if (el.__hoverMir) {
              return;
          }
          var elMirror = new el.constructor({
              style: el.style,
              shape: el.shape,
              z: el.z,
              z2: el.z2,
              silent: el.silent
          });
          elMirror.__from = el;
          el.__hoverMir = elMirror;
          hoverStyle && elMirror.setStyle(hoverStyle);
          this._hoverElements.push(elMirror);

          return elMirror;
      },

      removeHover: function (el) {
          var elMirror = el.__hoverMir;
          var hoverElements = this._hoverElements;
          var idx = indexOf(hoverElements, elMirror);
          if (idx >= 0) {
              hoverElements.splice(idx, 1);
          }
          el.__hoverMir = null;
      },

      clearHover: function (el) {
          var hoverElements = this._hoverElements;
          for (var i = 0; i < hoverElements.length; i++) {
              var from = hoverElements[i].__from;
              if (from) {
                  from.__hoverMir = null;
              }
          }
          hoverElements.length = 0;
      },

      refreshHover: function () {
          var hoverElements = this._hoverElements;
          var len = hoverElements.length;
          var hoverLayer = this._hoverlayer;
          hoverLayer && hoverLayer.clear();

          if (!len) {
              return;
          }
          sort(hoverElements, this.storage.displayableSortFunc);

          // Use a extream large zlevel
          // FIXME?
          if (!hoverLayer) {
              hoverLayer = this._hoverlayer = this.getLayer(HOVER_LAYER_ZLEVEL);
          }

          var scope = {};
          hoverLayer.ctx.save();
          for (var i = 0; i < len;) {
              var el = hoverElements[i];
              var originalEl = el.__from;
              // Original el is removed
              // PENDING
              if (!(originalEl && originalEl.__zr)) {
                  hoverElements.splice(i, 1);
                  originalEl.__hoverMir = null;
                  len--;
                  continue;
              }
              i++;

              // Use transform
              // FIXME style and shape ?
              if (!originalEl.invisible) {
                  el.transform = originalEl.transform;
                  el.invTransform = originalEl.invTransform;
                  el.__clipPaths = originalEl.__clipPaths;
                  // el.
                  this._doPaintEl(el, hoverLayer, true, scope);
              }
          }

          hoverLayer.ctx.restore();
      },

      getHoverLayer: function () {
          return this.getLayer(HOVER_LAYER_ZLEVEL);
      },

      _paintList: function (list, paintAll, redrawId) {
          if (this._redrawId !== redrawId) {
              return;
          }

          paintAll = paintAll || false;

          this._updateLayerStatus(list);

          var finished = this._doPaintList(list, paintAll);

          if (this._needsManuallyCompositing) {
              this._compositeManually();
          }

          if (!finished) {
              var self = this;
              requestAnimationFrame(function () {
                  self._paintList(list, paintAll, redrawId);
              });
          }
      },

      _compositeManually: function () {
          var ctx = this.getLayer(CANVAS_ZLEVEL).ctx;
          var width = this._domRoot.width;
          var height = this._domRoot.height;
          ctx.clearRect(0, 0, width, height);
          // PENDING, If only builtin layer?
          this.eachBuiltinLayer(function (layer) {
              if (layer.virtual) {
                  ctx.drawImage(layer.dom, 0, 0, width, height);
              }
          });
      },

      _doPaintList: function (list, paintAll) {
          var layerList = [];
          for (var zi = 0; zi < this._zlevelList.length; zi++) {
              var zlevel = this._zlevelList[zi];
              var layer = this._layers[zlevel];
              if (layer.__builtin__
                  && layer !== this._hoverlayer
                  && (layer.__dirty || paintAll)
              ) {
                  layerList.push(layer);
              }
          }

          var finished = true;

          for (var k = 0; k < layerList.length; k++) {
              var layer = layerList[k];
              var ctx = layer.ctx;
              var scope = {};
              ctx.save();

              var start = paintAll ? layer.__startIndex : layer.__drawIndex;

              var useTimer = !paintAll && layer.incremental && Date.now;
              var startTime = useTimer && Date.now();

              var clearColor = layer.zlevel === this._zlevelList[0]
                  ? this._backgroundColor : null;
              // All elements in this layer are cleared.
              if (layer.__startIndex === layer.__endIndex) {
                  layer.clear(false, clearColor);
              }
              else if (start === layer.__startIndex) {
                  var firstEl = list[start];
                  if (!firstEl.incremental || !firstEl.notClear || paintAll) {
                      layer.clear(false, clearColor);
                  }
              }
              if (start === -1) {
                  console.error('For some unknown reason. drawIndex is -1');
                  start = layer.__startIndex;
              }
              for (var i = start; i < layer.__endIndex; i++) {
                  var el = list[i];
                  this._doPaintEl(el, layer, paintAll, scope);
                  el.__dirty = el.__dirtyText = false;

                  if (useTimer) {
                      // Date.now can be executed in 13,025,305 ops/second.
                      var dTime = Date.now() - startTime;
                      // Give 15 millisecond to draw.
                      // The rest elements will be drawn in the next frame.
                      if (dTime > 15) {
                          break;
                      }
                  }
              }

              layer.__drawIndex = i;

              if (layer.__drawIndex < layer.__endIndex) {
                  finished = false;
              }

              if (scope.prevElClipPaths) {
                  // Needs restore the state. If last drawn element is in the clipping area.
                  ctx.restore();
              }

              ctx.restore();
          }

          if (env$1.wxa) {
              // Flush for weixin application
              each(this._layers, function (layer) {
                  if (layer && layer.ctx && layer.ctx.draw) {
                      layer.ctx.draw();
                  }
              });
          }

          return finished;
      },

      _doPaintEl: function (el, currentLayer, forcePaint, scope) {
          var ctx = currentLayer.ctx;
          var m = el.transform;
          if (
              (currentLayer.__dirty || forcePaint)
              // Ignore invisible element
              && !el.invisible
              // Ignore transparent element
              && el.style.opacity !== 0
              // Ignore scale 0 element, in some environment like node-canvas
              // Draw a scale 0 element can cause all following draw wrong
              // And setTransform with scale 0 will cause set back transform failed.
              && !(m && !m[0] && !m[3])
              // Ignore culled element
              && !(el.culling && isDisplayableCulled(el, this._width, this._height))
          ) {

              var clipPaths = el.__clipPaths;

              // Optimize when clipping on group with several elements
              if (!scope.prevElClipPaths
                  || isClipPathChanged(clipPaths, scope.prevElClipPaths)
              ) {
                  // If has previous clipping state, restore from it
                  if (scope.prevElClipPaths) {
                      currentLayer.ctx.restore();
                      scope.prevElClipPaths = null;

                      // Reset prevEl since context has been restored
                      scope.prevEl = null;
                  }
                  // New clipping state
                  if (clipPaths) {
                      ctx.save();
                      doClip(clipPaths, ctx);
                      scope.prevElClipPaths = clipPaths;
                  }
              }
              el.beforeBrush && el.beforeBrush(ctx);

              el.brush(ctx, scope.prevEl || null);
              scope.prevEl = el;

              el.afterBrush && el.afterBrush(ctx);
          }
      },

      /**
       * 获取 zlevel 所在层，如果不存在则会创建一个新的层
       * @param {number} zlevel
       * @param {boolean} virtual Virtual layer will not be inserted into dom.
       * @return {module:zrender/Layer}
       */
      getLayer: function (zlevel, virtual) {
          if (this._singleCanvas && !this._needsManuallyCompositing) {
              zlevel = CANVAS_ZLEVEL;
          }
          var layer = this._layers[zlevel];
          if (!layer) {
              // Create a new layer
              layer = new Layer('zr_' + zlevel, this, this.dpr);
              layer.zlevel = zlevel;
              layer.__builtin__ = true;

              if (this._layerConfig[zlevel]) {
                  merge(layer, this._layerConfig[zlevel], true);
              }

              if (virtual) {
                  layer.virtual = virtual;
              }

              this.insertLayer(zlevel, layer);

              // Context is created after dom inserted to document
              // Or excanvas will get 0px clientWidth and clientHeight
              layer.initContext();
          }

          return layer;
      },

      insertLayer: function (zlevel, layer) {

          var layersMap = this._layers;
          var zlevelList = this._zlevelList;
          var len = zlevelList.length;
          var prevLayer = null;
          var i = -1;
          var domRoot = this._domRoot;

          if (layersMap[zlevel]) {
              return;
          }
          // Check if is a valid layer
          if (!isLayerValid(layer)) {
              return;
          }

          if (len > 0 && zlevel > zlevelList[0]) {
              for (i = 0; i < len - 1; i++) {
                  if (
                      zlevelList[i] < zlevel
                      && zlevelList[i + 1] > zlevel
                  ) {
                      break;
                  }
              }
              prevLayer = layersMap[zlevelList[i]];
          }
          zlevelList.splice(i + 1, 0, zlevel);

          layersMap[zlevel] = layer;

          // Vitual layer will not directly show on the screen.
          // (It can be a WebGL layer and assigned to a ZImage element)
          // But it still under management of zrender.
          if (!layer.virtual) {
              if (prevLayer) {
                  var prevDom = prevLayer.dom;
                  if (prevDom.nextSibling) {
                      domRoot.insertBefore(
                          layer.dom,
                          prevDom.nextSibling
                      );
                  }
                  else {
                      domRoot.appendChild(layer.dom);
                  }
              }
              else {
                  if (domRoot.firstChild) {
                      domRoot.insertBefore(layer.dom, domRoot.firstChild);
                  }
                  else {
                      domRoot.appendChild(layer.dom);
                  }
              }
          }
      },

      // Iterate each layer
      eachLayer: function (cb, context) {
          var zlevelList = this._zlevelList;
          var z;
          var i;
          for (i = 0; i < zlevelList.length; i++) {
              z = zlevelList[i];
              cb.call(context, this._layers[z], z);
          }
      },

      // Iterate each buildin layer
      eachBuiltinLayer: function (cb, context) {
          var zlevelList = this._zlevelList;
          var layer;
          var z;
          var i;
          for (i = 0; i < zlevelList.length; i++) {
              z = zlevelList[i];
              layer = this._layers[z];
              if (layer.__builtin__) {
                  cb.call(context, layer, z);
              }
          }
      },

      // Iterate each other layer except buildin layer
      eachOtherLayer: function (cb, context) {
          var zlevelList = this._zlevelList;
          var layer;
          var z;
          var i;
          for (i = 0; i < zlevelList.length; i++) {
              z = zlevelList[i];
              layer = this._layers[z];
              if (!layer.__builtin__) {
                  cb.call(context, layer, z);
              }
          }
      },

      /**
       * 获取所有已创建的层
       * @param {Array.<module:zrender/Layer>} [prevLayer]
       */
      getLayers: function () {
          return this._layers;
      },

      _updateLayerStatus: function (list) {

          this.eachBuiltinLayer(function (layer, z) {
              layer.__dirty = layer.__used = false;
          });

          function updatePrevLayer(idx) {
              if (prevLayer) {
                  if (prevLayer.__endIndex !== idx) {
                      prevLayer.__dirty = true;
                  }
                  prevLayer.__endIndex = idx;
              }
          }

          if (this._singleCanvas) {
              for (var i = 1; i < list.length; i++) {
                  var el = list[i];
                  if (el.zlevel !== list[i - 1].zlevel || el.incremental) {
                      this._needsManuallyCompositing = true;
                      break;
                  }
              }
          }

          var prevLayer = null;
          var incrementalLayerCount = 0;
          for (var i = 0; i < list.length; i++) {
              var el = list[i];
              var zlevel = el.zlevel;
              var layer;
              // PENDING If change one incremental element style ?
              // TODO Where there are non-incremental elements between incremental elements.
              if (el.incremental) {
                  layer = this.getLayer(zlevel + INCREMENTAL_INC, this._needsManuallyCompositing);
                  layer.incremental = true;
                  incrementalLayerCount = 1;
              }
              else {
                  layer = this.getLayer(zlevel + (incrementalLayerCount > 0 ? EL_AFTER_INCREMENTAL_INC : 0), this._needsManuallyCompositing);
              }

              if (!layer.__builtin__) {
                  log('ZLevel ' + zlevel + ' has been used by unkown layer ' + layer.id);
              }

              if (layer !== prevLayer) {
                  layer.__used = true;
                  if (layer.__startIndex !== i) {
                      layer.__dirty = true;
                  }
                  layer.__startIndex = i;
                  if (!layer.incremental) {
                      layer.__drawIndex = i;
                  }
                  else {
                      // Mark layer draw index needs to update.
                      layer.__drawIndex = -1;
                  }
                  updatePrevLayer(i);
                  prevLayer = layer;
              }
              if (el.__dirty) {
                  layer.__dirty = true;
                  if (layer.incremental && layer.__drawIndex < 0) {
                      // Start draw from the first dirty element.
                      layer.__drawIndex = i;
                  }
              }
          }

          updatePrevLayer(i);

          this.eachBuiltinLayer(function (layer, z) {
              // Used in last frame but not in this frame. Needs clear
              if (!layer.__used && layer.getElementCount() > 0) {
                  layer.__dirty = true;
                  layer.__startIndex = layer.__endIndex = layer.__drawIndex = 0;
              }
              // For incremental layer. In case start index changed and no elements are dirty.
              if (layer.__dirty && layer.__drawIndex < 0) {
                  layer.__drawIndex = layer.__startIndex;
              }
          });
      },

      /**
       * 清除hover层外所有内容
       */
      clear: function () {
          this.eachBuiltinLayer(this._clearLayer);
          return this;
      },

      _clearLayer: function (layer) {
          layer.clear();
      },

      setBackgroundColor: function (backgroundColor) {
          this._backgroundColor = backgroundColor;
      },

      /**
       * 修改指定zlevel的绘制参数
       *
       * @param {string} zlevel
       * @param {Object} config 配置对象
       * @param {string} [config.clearColor=0] 每次清空画布的颜色
       * @param {string} [config.motionBlur=false] 是否开启动态模糊
       * @param {number} [config.lastFrameAlpha=0.7]
       *                 在开启动态模糊的时候使用，与上一帧混合的alpha值，值越大尾迹越明显
       */
      configLayer: function (zlevel, config) {
          if (config) {
              var layerConfig = this._layerConfig;
              if (!layerConfig[zlevel]) {
                  layerConfig[zlevel] = config;
              }
              else {
                  merge(layerConfig[zlevel], config, true);
              }

              for (var i = 0; i < this._zlevelList.length; i++) {
                  var _zlevel = this._zlevelList[i];
                  if (_zlevel === zlevel || _zlevel === zlevel + EL_AFTER_INCREMENTAL_INC) {
                      var layer = this._layers[_zlevel];
                      merge(layer, layerConfig[zlevel], true);
                  }
              }
          }
      },

      /**
       * 删除指定层
       * @param {number} zlevel 层所在的zlevel
       */
      delLayer: function (zlevel) {
          var layers = this._layers;
          var zlevelList = this._zlevelList;
          var layer = layers[zlevel];
          if (!layer) {
              return;
          }
          layer.dom.parentNode.removeChild(layer.dom);
          delete layers[zlevel];

          zlevelList.splice(indexOf(zlevelList, zlevel), 1);
      },

      /**
       * 区域大小变化后重绘
       */
      resize: function (width, height) {
          if (!this._domRoot.style) { // Maybe in node or worker
              if (width == null || height == null) {
                  return;
              }
              this._width = width;
              this._height = height;

              this.getLayer(CANVAS_ZLEVEL).resize(width, height);
          }
          else {
              var domRoot = this._domRoot;
              // FIXME Why ?
              domRoot.style.display = 'none';

              // Save input w/h
              var opts = this._opts;
              width != null && (opts.width = width);
              height != null && (opts.height = height);

              width = this._getSize(0);
              height = this._getSize(1);

              domRoot.style.display = '';

              // 优化没有实际改变的resize
              if (this._width !== width || height !== this._height) {
                  domRoot.style.width = width + 'px';
                  domRoot.style.height = height + 'px';

                  for (var id in this._layers) {
                      if (this._layers.hasOwnProperty(id)) {
                          this._layers[id].resize(width, height);
                      }
                  }
                  each(this._progressiveLayers, function (layer) {
                      layer.resize(width, height);
                  });

                  this.refresh(true);
              }

              this._width = width;
              this._height = height;

          }
          return this;
      },

      /**
       * 清除单独的一个层
       * @param {number} zlevel
       */
      clearLayer: function (zlevel) {
          var layer = this._layers[zlevel];
          if (layer) {
              layer.clear();
          }
      },

      /**
       * 释放
       */
      dispose: function () {
          this.root.innerHTML = '';

          this.root =
          this.storage =

          this._domRoot =
          this._layers = null;
      },

      /**
       * Get canvas which has all thing rendered
       * @param {Object} opts
       * @param {string} [opts.backgroundColor]
       * @param {number} [opts.pixelRatio]
       */
      getRenderedCanvas: function (opts) {
          opts = opts || {};
          if (this._singleCanvas && !this._compositeManually) {
              return this._layers[CANVAS_ZLEVEL].dom;
          }

          var imageLayer = new Layer('image', this, opts.pixelRatio || this.dpr);
          imageLayer.initContext();
          imageLayer.clear(false, opts.backgroundColor || this._backgroundColor);

          if (opts.pixelRatio <= this.dpr) {
              this.refresh();

              var width = imageLayer.dom.width;
              var height = imageLayer.dom.height;
              var ctx = imageLayer.ctx;
              this.eachLayer(function (layer) {
                  if (layer.__builtin__) {
                      ctx.drawImage(layer.dom, 0, 0, width, height);
                  }
                  else if (layer.renderToCanvas) {
                      imageLayer.ctx.save();
                      layer.renderToCanvas(imageLayer.ctx);
                      imageLayer.ctx.restore();
                  }
              });
          }
          else {
              // PENDING, echarts-gl and incremental rendering.
              var scope = {};
              var displayList = this.storage.getDisplayList(true);
              for (var i = 0; i < displayList.length; i++) {
                  var el = displayList[i];
                  this._doPaintEl(el, imageLayer, true, scope);
              }
          }

          return imageLayer.dom;
      },
      /**
       * 获取绘图区域宽度
       */
      getWidth: function () {
          return this._width;
      },

      /**
       * 获取绘图区域高度
       */
      getHeight: function () {
          return this._height;
      },

      _getSize: function (whIdx) {
          var opts = this._opts;
          var wh = ['width', 'height'][whIdx];
          var cwh = ['clientWidth', 'clientHeight'][whIdx];
          var plt = ['paddingLeft', 'paddingTop'][whIdx];
          var prb = ['paddingRight', 'paddingBottom'][whIdx];

          if (opts[wh] != null && opts[wh] !== 'auto') {
              return parseFloat(opts[wh]);
          }

          var root = this.root;
          // IE8 does not support getComputedStyle, but it use VML.
          var stl = document.defaultView.getComputedStyle(root);

          return (
              (root[cwh] || parseInt10(stl[wh]) || parseInt10(root.style[wh]))
              - (parseInt10(stl[plt]) || 0)
              - (parseInt10(stl[prb]) || 0)
          ) | 0;
      },

      pathToImage: function (path, dpr) {
          dpr = dpr || this.dpr;

          var canvas = document.createElement('canvas');
          var ctx = canvas.getContext('2d');
          var rect = path.getBoundingRect();
          var style = path.style;
          var shadowBlurSize = style.shadowBlur * dpr;
          var shadowOffsetX = style.shadowOffsetX * dpr;
          var shadowOffsetY = style.shadowOffsetY * dpr;
          var lineWidth = style.hasStroke() ? style.lineWidth : 0;

          var leftMargin = Math.max(lineWidth / 2, -shadowOffsetX + shadowBlurSize);
          var rightMargin = Math.max(lineWidth / 2, shadowOffsetX + shadowBlurSize);
          var topMargin = Math.max(lineWidth / 2, -shadowOffsetY + shadowBlurSize);
          var bottomMargin = Math.max(lineWidth / 2, shadowOffsetY + shadowBlurSize);
          var width = rect.width + leftMargin + rightMargin;
          var height = rect.height + topMargin + bottomMargin;

          canvas.width = width * dpr;
          canvas.height = height * dpr;

          ctx.scale(dpr, dpr);
          ctx.clearRect(0, 0, width, height);
          ctx.dpr = dpr;

          var pathTransform = {
              position: path.position,
              rotation: path.rotation,
              scale: path.scale
          };
          path.position = [leftMargin - rect.x, topMargin - rect.y];
          path.rotation = 0;
          path.scale = [1, 1];
          path.updateTransform();
          if (path) {
              path.brush(ctx);
          }

          var ImageShape = ZImage;
          var imgShape = new ImageShape({
              style: {
                  x: 0,
                  y: 0,
                  image: canvas
              }
          });

          if (pathTransform.position != null) {
              imgShape.position = path.position = pathTransform.position;
          }

          if (pathTransform.rotation != null) {
              imgShape.rotation = path.rotation = pathTransform.rotation;
          }

          if (pathTransform.scale != null) {
              imgShape.scale = path.scale = pathTransform.scale;
          }

          return imgShape;
      }
  };

  /**
   * 动画主类, 调度和管理所有动画控制器
   *
   * @module zrender/animation/Animation
   * @author pissang(https://github.com/pissang)
   */

  /**
   * @typedef {Object} IZRenderStage
   * @property {Function} update
   */

  /**
   * @alias module:zrender/animation/Animation
   * @constructor
   * @param {Object} [options]
   * @param {Function} [options.onframe]
   * @param {IZRenderStage} [options.stage]
   * @example
   *     var animation = new Animation();
   *     var obj = {
   *         x: 100,
   *         y: 100
   *     };
   *     animation.animate(node.position)
   *         .when(1000, {
   *             x: 500,
   *             y: 500
   *         })
   *         .when(2000, {
   *             x: 100,
   *             y: 100
   *         })
   *         .start('spline');
   */
  var Animation = function (options) {

      options = options || {};

      this.stage = options.stage || {};

      this.onframe = options.onframe || function () {};

      // private properties
      this._clips = [];

      this._running = false;

      this._time;

      this._pausedTime;

      this._pauseStart;

      this._paused = false;

      Eventful.call(this);
  };

  Animation.prototype = {

      constructor: Animation,
      /**
       * 添加 clip
       * @param {module:zrender/animation/Clip} clip
       */
      addClip: function (clip) {
          this._clips.push(clip);
      },
      /**
       * 添加 animator
       * @param {module:zrender/animation/Animator} animator
       */
      addAnimator: function (animator) {
          animator.animation = this;
          var clips = animator.getClips();
          for (var i = 0; i < clips.length; i++) {
              this.addClip(clips[i]);
          }
      },
      /**
       * 删除动画片段
       * @param {module:zrender/animation/Clip} clip
       */
      removeClip: function (clip) {
          var idx = indexOf(this._clips, clip);
          if (idx >= 0) {
              this._clips.splice(idx, 1);
          }
      },

      /**
       * 删除动画片段
       * @param {module:zrender/animation/Animator} animator
       */
      removeAnimator: function (animator) {
          var clips = animator.getClips();
          for (var i = 0; i < clips.length; i++) {
              this.removeClip(clips[i]);
          }
          animator.animation = null;
      },

      _update: function () {
          var time = new Date().getTime() - this._pausedTime;
          var delta = time - this._time;
          var clips = this._clips;
          var len = clips.length;

          var deferredEvents = [];
          var deferredClips = [];
          for (var i = 0; i < len; i++) {
              var clip = clips[i];
              var e = clip.step(time, delta);
              // Throw out the events need to be called after
              // stage.update, like destroy
              if (e) {
                  deferredEvents.push(e);
                  deferredClips.push(clip);
              }
          }

          // Remove the finished clip
          for (var i = 0; i < len;) {
              if (clips[i]._needsRemove) {
                  clips[i] = clips[len - 1];
                  clips.pop();
                  len--;
              }
              else {
                  i++;
              }
          }

          len = deferredEvents.length;
          for (var i = 0; i < len; i++) {
              deferredClips[i].fire(deferredEvents[i]);
          }

          this._time = time;

          this.onframe(delta);

          // 'frame' should be triggered before stage, because upper application
          // depends on the sequence (e.g., echarts-stream and finish
          // event judge)
          this.trigger('frame', delta);

          if (this.stage.update) {
              this.stage.update();
          }
      },

      _startLoop: function () {
          var self = this;

          this._running = true;

          function step() {
              if (self._running) {

                  requestAnimationFrame(step);

                  !self._paused && self._update();
              }
          }

          requestAnimationFrame(step);
      },

      /**
       * Start animation.
       */
      start: function () {

          this._time = new Date().getTime();
          this._pausedTime = 0;

          this._startLoop();
      },

      /**
       * Stop animation.
       */
      stop: function () {
          this._running = false;
      },

      /**
       * Pause animation.
       */
      pause: function () {
          if (!this._paused) {
              this._pauseStart = new Date().getTime();
              this._paused = true;
          }
      },

      /**
       * Resume animation.
       */
      resume: function () {
          if (this._paused) {
              this._pausedTime += (new Date().getTime()) - this._pauseStart;
              this._paused = false;
          }
      },

      /**
       * Clear animation.
       */
      clear: function () {
          this._clips = [];
      },

      /**
       * Whether animation finished.
       */
      isFinished: function () {
          return !this._clips.length;
      },

      /**
       * Creat animator for a target, whose props can be animated.
       *
       * @param  {Object} target
       * @param  {Object} options
       * @param  {boolean} [options.loop=false] Whether loop animation.
       * @param  {Function} [options.getter=null] Get value from target.
       * @param  {Function} [options.setter=null] Set value to target.
       * @return {module:zrender/animation/Animation~Animator}
       */
      // TODO Gap
      animate: function (target, options) {
          options = options || {};

          var animator = new Animator(
              target,
              options.loop,
              options.getter,
              options.setter
          );

          this.addAnimator(animator);

          return animator;
      }
  };

  mixin(Animation, Eventful);

  var TOUCH_CLICK_DELAY = 300;

  var mouseHandlerNames = [
      'click', 'dblclick', 'mousewheel', 'mouseout',
      'mouseup', 'mousedown', 'mousemove', 'contextmenu'
  ];

  var touchHandlerNames = [
      'touchstart', 'touchend', 'touchmove'
  ];

  var pointerEventNames = {
      pointerdown: 1, pointerup: 1, pointermove: 1, pointerout: 1
  };

  var pointerHandlerNames = map(mouseHandlerNames, function (name) {
      var nm = name.replace('mouse', 'pointer');
      return pointerEventNames[nm] ? nm : name;
  });

  function eventNameFix(name) {
      return (name === 'mousewheel' && env$1.browser.firefox) ? 'DOMMouseScroll' : name;
  }

  // function onMSGestureChange(proxy, event) {
  //     if (event.translationX || event.translationY) {
  //         // mousemove is carried by MSGesture to reduce the sensitivity.
  //         proxy.handler.dispatchToElement(event.target, 'mousemove', event);
  //     }
  //     if (event.scale !== 1) {
  //         event.pinchX = event.offsetX;
  //         event.pinchY = event.offsetY;
  //         event.pinchScale = event.scale;
  //         proxy.handler.dispatchToElement(event.target, 'pinch', event);
  //     }
  // }

  /**
   * Prevent mouse event from being dispatched after Touch Events action
   * @see <https://github.com/deltakosh/handjs/blob/master/src/hand.base.js>
   * 1. Mobile browsers dispatch mouse events 300ms after touchend.
   * 2. Chrome for Android dispatch mousedown for long-touch about 650ms
   * Result: Blocking Mouse Events for 700ms.
   */
  function setTouchTimer(instance) {
      instance._touching = true;
      clearTimeout(instance._touchTimer);
      instance._touchTimer = setTimeout(function () {
          instance._touching = false;
      }, 700);
  }


  var domHandlers = {
      /**
       * Mouse move handler
       * @inner
       * @param {Event} event
       */
      mousemove: function (event) {
          event = normalizeEvent(this.dom, event);

          this.trigger('mousemove', event);
      },

      /**
       * Mouse out handler
       * @inner
       * @param {Event} event
       */
      mouseout: function (event) {
          event = normalizeEvent(this.dom, event);

          var element = event.toElement || event.relatedTarget;
          if (element !== this.dom) {
              while (element && element.nodeType !== 9) {
                  // 忽略包含在root中的dom引起的mouseOut
                  if (element === this.dom) {
                      return;
                  }

                  element = element.parentNode;
              }
          }

          this.trigger('mouseout', event);
      },

      /**
       * Touch开始响应函数
       * @inner
       * @param {Event} event
       */
      touchstart: function (event) {
          // Default mouse behaviour should not be disabled here.
          // For example, page may needs to be slided.
          event = normalizeEvent(this.dom, event);

          // Mark touch, which is useful in distinguish touch and
          // mouse event in upper applicatoin.
          event.zrByTouch = true;

          this._lastTouchMoment = new Date();

          this.handler.processGesture(this, event, 'start');

          // In touch device, trigger `mousemove`(`mouseover`) should
          // be triggered, and must before `mousedown` triggered.
          domHandlers.mousemove.call(this, event);

          domHandlers.mousedown.call(this, event);

          setTouchTimer(this);
      },

      /**
       * Touch移动响应函数
       * @inner
       * @param {Event} event
       */
      touchmove: function (event) {

          event = normalizeEvent(this.dom, event);

          // Mark touch, which is useful in distinguish touch and
          // mouse event in upper applicatoin.
          event.zrByTouch = true;

          this.handler.processGesture(this, event, 'change');

          // Mouse move should always be triggered no matter whether
          // there is gestrue event, because mouse move and pinch may
          // be used at the same time.
          domHandlers.mousemove.call(this, event);

          setTouchTimer(this);
      },

      /**
       * Touch结束响应函数
       * @inner
       * @param {Event} event
       */
      touchend: function (event) {

          event = normalizeEvent(this.dom, event);

          // Mark touch, which is useful in distinguish touch and
          // mouse event in upper applicatoin.
          event.zrByTouch = true;

          this.handler.processGesture(this, event, 'end');

          domHandlers.mouseup.call(this, event);

          // Do not trigger `mouseout` here, in spite of `mousemove`(`mouseover`) is
          // triggered in `touchstart`. This seems to be illogical, but by this mechanism,
          // we can conveniently implement "hover style" in both PC and touch device just
          // by listening to `mouseover` to add "hover style" and listening to `mouseout`
          // to remove "hover style" on an element, without any additional code for
          // compatibility. (`mouseout` will not be triggered in `touchend`, so "hover
          // style" will remain for user view)

          // click event should always be triggered no matter whether
          // there is gestrue event. System click can not be prevented.
          if (+new Date() - this._lastTouchMoment < TOUCH_CLICK_DELAY) {
              domHandlers.click.call(this, event);
          }

          setTouchTimer(this);
      },

      pointerdown: function (event) {
          domHandlers.mousedown.call(this, event);

          // if (useMSGuesture(this, event)) {
          //     this._msGesture.addPointer(event.pointerId);
          // }
      },

      pointermove: function (event) {
          // FIXME
          // pointermove is so sensitive that it always triggered when
          // tap(click) on touch screen, which affect some judgement in
          // upper application. So, we dont support mousemove on MS touch
          // device yet.
          if (!isPointerFromTouch(event)) {
              domHandlers.mousemove.call(this, event);
          }
      },

      pointerup: function (event) {
          domHandlers.mouseup.call(this, event);
      },

      pointerout: function (event) {
          // pointerout will be triggered when tap on touch screen
          // (IE11+/Edge on MS Surface) after click event triggered,
          // which is inconsistent with the mousout behavior we defined
          // in touchend. So we unify them.
          // (check domHandlers.touchend for detailed explanation)
          if (!isPointerFromTouch(event)) {
              domHandlers.mouseout.call(this, event);
          }
      }
  };

  function isPointerFromTouch(event) {
      var pointerType = event.pointerType;
      return pointerType === 'pen' || pointerType === 'touch';
  }

  // function useMSGuesture(handlerProxy, event) {
  //     return isPointerFromTouch(event) && !!handlerProxy._msGesture;
  // }

  // Common handlers
  each(['click', 'mousedown', 'mouseup', 'mousewheel', 'dblclick', 'contextmenu'], function (name) {
      domHandlers[name] = function (event) {
          event = normalizeEvent(this.dom, event);
          this.trigger(name, event);
      };
  });

  /**
   * 为控制类实例初始化dom 事件处理函数
   *
   * @inner
   * @param {module:zrender/Handler} instance 控制类实例
   */
  function initDomHandler(instance) {
      each(touchHandlerNames, function (name) {
          instance._handlers[name] = bind(domHandlers[name], instance);
      });

      each(pointerHandlerNames, function (name) {
          instance._handlers[name] = bind(domHandlers[name], instance);
      });

      each(mouseHandlerNames, function (name) {
          instance._handlers[name] = makeMouseHandler(domHandlers[name], instance);
      });

      function makeMouseHandler(fn, instance) {
          return function () {
              if (instance._touching) {
                  return;
              }
              return fn.apply(instance, arguments);
          };
      }
  }


  function HandlerDomProxy(dom) {
      Eventful.call(this);

      this.dom = dom;

      /**
       * @private
       * @type {boolean}
       */
      this._touching = false;

      /**
       * @private
       * @type {number}
       */
      this._touchTimer;

      this._handlers = {};

      initDomHandler(this);

      if (env$1.pointerEventsSupported) { // Only IE11+/Edge
          // 1. On devices that both enable touch and mouse (e.g., MS Surface and lenovo X240),
          // IE11+/Edge do not trigger touch event, but trigger pointer event and mouse event
          // at the same time.
          // 2. On MS Surface, it probablely only trigger mousedown but no mouseup when tap on
          // screen, which do not occurs in pointer event.
          // So we use pointer event to both detect touch gesture and mouse behavior.
          mountHandlers(pointerHandlerNames, this);

          // FIXME
          // Note: MS Gesture require CSS touch-action set. But touch-action is not reliable,
          // which does not prevent defuault behavior occasionally (which may cause view port
          // zoomed in but use can not zoom it back). And event.preventDefault() does not work.
          // So we have to not to use MSGesture and not to support touchmove and pinch on MS
          // touch screen. And we only support click behavior on MS touch screen now.

          // MS Gesture Event is only supported on IE11+/Edge and on Windows 8+.
          // We dont support touch on IE on win7.
          // See <https://msdn.microsoft.com/en-us/library/dn433243(v=vs.85).aspx>
          // if (typeof MSGesture === 'function') {
          //     (this._msGesture = new MSGesture()).target = dom; // jshint ignore:line
          //     dom.addEventListener('MSGestureChange', onMSGestureChange);
          // }
      }
      else {
          if (env$1.touchEventsSupported) {
              mountHandlers(touchHandlerNames, this);
              // Handler of 'mouseout' event is needed in touch mode, which will be mounted below.
              // addEventListener(root, 'mouseout', this._mouseoutHandler);
          }

          // 1. Considering some devices that both enable touch and mouse event (like on MS Surface
          // and lenovo X240, @see #2350), we make mouse event be always listened, otherwise
          // mouse event can not be handle in those devices.
          // 2. On MS Surface, Chrome will trigger both touch event and mouse event. How to prevent
          // mouseevent after touch event triggered, see `setTouchTimer`.
          mountHandlers(mouseHandlerNames, this);
      }

      function mountHandlers(handlerNames, instance) {
          each(handlerNames, function (name) {
              addEventListener(dom, eventNameFix(name), instance._handlers[name]);
          }, instance);
      }
  }

  var handlerDomProxyProto = HandlerDomProxy.prototype;
  handlerDomProxyProto.dispose = function () {
      var handlerNames = mouseHandlerNames.concat(touchHandlerNames);

      for (var i = 0; i < handlerNames.length; i++) {
          var name = handlerNames[i];
          removeEventListener(this.dom, eventNameFix(name), this._handlers[name]);
      }
  };

  handlerDomProxyProto.setCursor = function (cursorStyle) {
      this.dom.style && (this.dom.style.cursor = cursorStyle || 'default');
  };

  mixin(HandlerDomProxy, Eventful);

  /*!
  * ZRender, a high performance 2d drawing library.
  *
  * Copyright (c) 2013, Baidu Inc.
  * All rights reserved.
  *
  * LICENSE
  * https://github.com/ecomfe/zrender/blob/master/LICENSE.txt
  */

  var useVML = !env$1.canvasSupported;

  var painterCtors = {
      canvas: Painter
  };

  /**
   * Initializing a zrender instance
   * @param {HTMLElement} dom
   * @param {Object} [opts]
   * @param {string} [opts.renderer='canvas'] 'canvas' or 'svg'
   * @param {number} [opts.devicePixelRatio]
   * @param {number|string} [opts.width] Can be 'auto' (the same as null/undefined)
   * @param {number|string} [opts.height] Can be 'auto' (the same as null/undefined)
   * @return {module:zrender/ZRender}
   */
  function init(dom, opts) {
      var zr = new ZRender(guid(), dom, opts);
      return zr;
  }

  function delInstance(id) {
  }

  /**
   * @module zrender/ZRender
   */
  /**
   * @constructor
   * @alias module:zrender/ZRender
   * @param {string} id
   * @param {HTMLElement} dom
   * @param {Object} opts
   * @param {string} [opts.renderer='canvas'] 'canvas' or 'svg'
   * @param {number} [opts.devicePixelRatio]
   * @param {number} [opts.width] Can be 'auto' (the same as null/undefined)
   * @param {number} [opts.height] Can be 'auto' (the same as null/undefined)
   */
  var ZRender = function (id, dom, opts) {

      opts = opts || {};

      /**
       * @type {HTMLDomElement}
       */
      this.dom = dom;

      /**
       * @type {string}
       */
      this.id = id;

      var self = this;
      var storage = new Storage();

      var rendererType = opts.renderer;
      // TODO WebGL
      if (useVML) {
          {
              throw new Error('You need to require \'zrender/vml/vml\' to support IE8');
          }
      }
      else if (!rendererType || !painterCtors[rendererType]) {
          rendererType = 'canvas';
      }
      var painter = new painterCtors[rendererType](dom, storage, opts, id);

      this.storage = storage;
      this.painter = painter;

      var handerProxy = (!env$1.node && !env$1.worker) ? new HandlerDomProxy(painter.getViewportRoot()) : null;
      this.handler = new Handler(storage, painter, handerProxy, painter.root);

      /**
       * @type {module:zrender/animation/Animation}
       */
      this.animation = new Animation({
          stage: {
              update: bind(this.flush, this)
          }
      });
      this.animation.start();

      /**
       * @type {boolean}
       * @private
       */
      this._needsRefresh;

      // 修改 storage.delFromStorage, 每次删除元素之前删除动画
      // FIXME 有点ugly
      var oldDelFromStorage = storage.delFromStorage;
      var oldAddToStorage = storage.addToStorage;

      storage.delFromStorage = function (el) {
          oldDelFromStorage.call(storage, el);

          el && el.removeSelfFromZr(self);
      };

      storage.addToStorage = function (el) {
          oldAddToStorage.call(storage, el);

          el.addSelfToZr(self);
      };
  };

  ZRender.prototype = {

      constructor: ZRender,
      /**
       * 获取实例唯一标识
       * @return {string}
       */
      getId: function () {
          return this.id;
      },

      /**
       * 添加元素
       * @param  {module:zrender/Element} el
       */
      add: function (el) {
          this.storage.addRoot(el);
          this._needsRefresh = true;
      },

      /**
       * 删除元素
       * @param  {module:zrender/Element} el
       */
      remove: function (el) {
          this.storage.delRoot(el);
          this._needsRefresh = true;
      },

      /**
       * Change configuration of layer
       * @param {string} zLevel
       * @param {Object} config
       * @param {string} [config.clearColor=0] Clear color
       * @param {string} [config.motionBlur=false] If enable motion blur
       * @param {number} [config.lastFrameAlpha=0.7] Motion blur factor. Larger value cause longer trailer
      */
      configLayer: function (zLevel, config) {
          if (this.painter.configLayer) {
              this.painter.configLayer(zLevel, config);
          }
          this._needsRefresh = true;
      },

      /**
       * Set background color
       * @param {string} backgroundColor
       */
      setBackgroundColor: function (backgroundColor) {
          if (this.painter.setBackgroundColor) {
              this.painter.setBackgroundColor(backgroundColor);
          }
          this._needsRefresh = true;
      },

      /**
       * Repaint the canvas immediately
       */
      refreshImmediately: function () {
          // var start = new Date();
          // Clear needsRefresh ahead to avoid something wrong happens in refresh
          // Or it will cause zrender refreshes again and again.
          this._needsRefresh = false;
          this.painter.refresh();
          /**
           * Avoid trigger zr.refresh in Element#beforeUpdate hook
           */
          this._needsRefresh = false;
          // var end = new Date();
          // var log = document.getElementById('log');
          // if (log) {
          //     log.innerHTML = log.innerHTML + '<br>' + (end - start);
          // }
      },

      /**
       * Mark and repaint the canvas in the next frame of browser
       */
      refresh: function () {
          this._needsRefresh = true;
      },

      /**
       * Perform all refresh
       */
      flush: function () {
          var triggerRendered;

          if (this._needsRefresh) {
              triggerRendered = true;
              this.refreshImmediately();
          }
          if (this._needsRefreshHover) {
              triggerRendered = true;
              this.refreshHoverImmediately();
          }

          triggerRendered && this.trigger('rendered');
      },

      /**
       * Add element to hover layer
       * @param  {module:zrender/Element} el
       * @param {Object} style
       */
      addHover: function (el, style) {
          if (this.painter.addHover) {
              var elMirror = this.painter.addHover(el, style);
              this.refreshHover();
              return elMirror;
          }
      },

      /**
       * Add element from hover layer
       * @param  {module:zrender/Element} el
       */
      removeHover: function (el) {
          if (this.painter.removeHover) {
              this.painter.removeHover(el);
              this.refreshHover();
          }
      },

      /**
       * Clear all hover elements in hover layer
       * @param  {module:zrender/Element} el
       */
      clearHover: function () {
          if (this.painter.clearHover) {
              this.painter.clearHover();
              this.refreshHover();
          }
      },

      /**
       * Refresh hover in next frame
       */
      refreshHover: function () {
          this._needsRefreshHover = true;
      },

      /**
       * Refresh hover immediately
       */
      refreshHoverImmediately: function () {
          this._needsRefreshHover = false;
          this.painter.refreshHover && this.painter.refreshHover();
      },

      /**
       * Resize the canvas.
       * Should be invoked when container size is changed
       * @param {Object} [opts]
       * @param {number|string} [opts.width] Can be 'auto' (the same as null/undefined)
       * @param {number|string} [opts.height] Can be 'auto' (the same as null/undefined)
       */
      resize: function (opts) {
          opts = opts || {};
          this.painter.resize(opts.width, opts.height);
          this.handler.resize();
      },

      /**
       * Stop and clear all animation immediately
       */
      clearAnimation: function () {
          this.animation.clear();
      },

      /**
       * Get container width
       */
      getWidth: function () {
          return this.painter.getWidth();
      },

      /**
       * Get container height
       */
      getHeight: function () {
          return this.painter.getHeight();
      },

      /**
       * Export the canvas as Base64 URL
       * @param {string} type
       * @param {string} [backgroundColor='#fff']
       * @return {string} Base64 URL
       */
      // toDataURL: function(type, backgroundColor) {
      //     return this.painter.getRenderedCanvas({
      //         backgroundColor: backgroundColor
      //     }).toDataURL(type);
      // },

      /**
       * Converting a path to image.
       * It has much better performance of drawing image rather than drawing a vector path.
       * @param {module:zrender/graphic/Path} e
       * @param {number} width
       * @param {number} height
       */
      pathToImage: function (e, dpr) {
          return this.painter.pathToImage(e, dpr);
      },

      /**
       * Set default cursor
       * @param {string} [cursorStyle='default'] 例如 crosshair
       */
      setCursorStyle: function (cursorStyle) {
          this.handler.setCursorStyle(cursorStyle);
      },

      /**
       * Find hovered element
       * @param {number} x
       * @param {number} y
       * @return {Object} {target, topTarget}
       */
      findHover: function (x, y) {
          return this.handler.findHover(x, y);
      },

      /**
       * Bind event
       *
       * @param {string} eventName Event name
       * @param {Function} eventHandler Handler function
       * @param {Object} [context] Context object
       */
      on: function (eventName, eventHandler, context) {
          this.handler.on(eventName, eventHandler, context);
      },

      /**
       * Unbind event
       * @param {string} eventName Event name
       * @param {Function} [eventHandler] Handler function
       */
      off: function (eventName, eventHandler) {
          this.handler.off(eventName, eventHandler);
      },

      /**
       * Trigger event manually
       *
       * @param {string} eventName Event name
       * @param {event=} event Event object
       */
      trigger: function (eventName, event) {
          this.handler.trigger(eventName, event);
      },


      /**
       * Clear all objects and the canvas.
       */
      clear: function () {
          this.storage.delRoot();
          this.painter.clear();
      },

      /**
       * Dispose self.
       */
      dispose: function () {
          this.animation.stop();

          this.clear();
          this.storage.dispose();
          this.painter.dispose();
          this.handler.dispose();

          this.animation =
          this.storage =
          this.painter =
          this.handler = null;

          delInstance(this.id);
      }
  };

  /**
   * @zhangnt
   * 	右键菜单
   *  配置对象如下
   *
   *  contextMenu:function(target){
   *      return{
   *          handler:function(target,id){
   *
   *          },
   *          menu:[{
   *              id:xxx,
   *              label:xxx,
   *              handler:function(target){
   *
   *              }
   *          },{
   *              childs:[{
   *                  id:xxx,
   *                  label:xxx,
   *                  handler:function(target){
   *
   *                  }
   *              }]
   *          }]
   *      }
   *  }
   *
   */
  var ContextMenu = /*#__PURE__*/function () {
    function ContextMenu(menuConfig, UTopoView) {
      _classCallCheck(this, ContextMenu);

      //topo对象
      this._view = UTopoView; //zr的div

      this.id = UTopoView._dom.id + "_utopo_contextmenu"; //contextMenu配置

      this.config = menuConfig; //dom对象

      this._dom = null; //contextMenu初始化

      this.init();
    }
    /**
     * 初始化方法,为zrender添加几个事件.
     * 鼠标:
     * 		移入:显示tooltip
     * 		移出:隐藏tooltip
     * 		按下:不允许显示
     * 		放开:允许显示
     */


    _createClass(ContextMenu, [{
      key: "init",
      value: function init() {
        var _this = this;

        this.initContextMenuDom(); //zrender对象

        var _zrender = this._view._zrender; //获取tooltip对象
        //阻止事件冒泡

        this._view._dom.oncontextmenu = function (e) {
          return false;
        }; //注册单击事件,关闭右键菜单


        _zrender.on("click", function (e) {
          if (_this._view._tooltip) {
            _this._view._tooltip.hide(1);
          }

          _this.hide();
        }); //鼠标右键菜单事件


        _zrender.on("mousedown", function (e) {
          if (_this._view._tooltip) {
            _this._view._tooltip.hide(1);
          }

          if (e.event.button === 2) {
            var target = e.target; //触发对象存在

            if (target && target.model || target.type === 'evview') {
              var menuData = _this.config(target.model);

              if (menuData != null) {
                menuData.target = target.model;
                var eventPosition = {
                  zrX: e.event.zrX,
                  zrY: e.event.zrY,
                  pageX: e.event.pageX,
                  pageY: e.event.pageY
                }; //展现

                _this.show(eventPosition, menuData);
              }
            } else {
              return false;
            }
          } else {
            _this.hide();
          }
        });
      }
      /**
       * 初始化右键菜单的dom对象
       */

    }, {
      key: "initContextMenuDom",
      value: function initContextMenuDom() {
        this._dom = document.getElementById(this.id);

        if (this._dom == null) {
          this._dom = document.createElement("ul");

          this._dom.setAttribute("id", this.id);

          this._dom.setAttribute("class", "utopo_zrender_contextmenu");

          document.body.appendChild(this._dom);

          this._dom.onclick = function () {
            this.style.display = "none";
          };

          this._dom.oncontextmenu = function () {
            return false;
          };
        }
      }
      /**
       * 显示方法
       */

    }, {
      key: "show",
      value: function show(eventP, menuData) {
        if (menuData == null || menuData === "") {
          return;
        } //兼容之前，是数组的话


        if (menuData.menu === undefined || menuData.menu == null || menuData.menu.length === 0) {
          return;
        } //创建


        this.createMenu(menuData); //显示

        this._dom.style.display = "block";
        this.isShow = true; //设置位置

        this.setPosition(eventP);
      }
      /**
       * 隐藏方法
       */

    }, {
      key: "hide",
      value: function hide() {
        if (this._dom) {
          this.isShow = false;
          this._dom.style.display = "none";
        }
      }
      /**
       * 创建右键菜单数据
       */

    }, {
      key: "createMenu",
      value: function createMenu(menuData) {
        //清空
        this._dom.innerHTML = ""; //为html元素添加menu数据

        this.createMenuWithObject(menuData);
      }
      /**
       * 以对象方式进行配置
       */

    }, {
      key: "createMenuWithObject",
      value: function createMenuWithObject(menuData) {
        var _this2 = this;

        var globalHandler = menuData.handler;
        var target = menuData.target;
        var menus = menuData.menu;

        if (!menus) {
          //如果没有配置,则返回
          return;
        } //循环


        menus.forEach(function (menu) {
          var li = document.createElement("li");
          li.innerHTML = menu.label;
          var childs = menu.childs; //创建二级菜单

          if (childs) {
            //创建二级菜单,三角标识
            var nextArrow = document.createElement("span");
            nextArrow.setAttribute("class", "utopo_zrender_contextmenu_nextarrow");
            li.appendChild(nextArrow);
            var nextUl = document.createElement("ul");
            childs.forEach(function (menu) {
              var nextLi = document.createElement("li");
              nextLi.innerHTML = menu.label;

              if (menu.handler) {
                var handler = menu.handler;

                nextLi.onclick = function () {
                  handler(target);
                };
              } else {
                nextLi.onclick = function () {
                  globalHandler(target, menu.id);
                };
              }

              nextUl.appendChild(nextLi);
            });
            li.appendChild(nextUl);
          } else {
            //只有一级菜单
            if (menu.handler) {
              var handler = menu.handler;

              li.onclick = function () {
                handler(target);
              };
            } else {
              li.onclick = function () {
                globalHandler(target, menu.id);
              };
            }
          }

          _this2._dom.appendChild(li);
        });
      }
      /**
       * 设置position位置(鼠标右键弹出位置)
       */

    }, {
      key: "setPosition",
      value: function setPosition(eventP) {
        //x,y  是  鼠标处在zr绘图区域左上角的相对位置
        var x = eventP.zrX;
        var y = eventP.zrY; //pageX，pageY 是鼠标处在dom页面的真实x，y

        var pageX = eventP.pageX;
        var pageY = eventP.pageY; //依据x,y来计算pageX,pageY是否需要修改

        if (x + domWidth > this._view.getWidth()) {
          pageX -= domWidth + 5;
        } else {
          pageX += 5;
        }

        if (y + domHeight > this._view.getHeight()) {
          pageY -= domHeight + 5;
        } else {
          pageX += 5;
        }

        this._dom.style.left = pageX + "px";
        this._dom.style.top = pageY + "px";
      }
    }]);

    return ContextMenu;
  }();

  // 7.2.8 IsRegExp(argument)


  var MATCH = _wks('match');
  var _isRegexp = function (it) {
    var isRegExp;
    return _isObject(it) && ((isRegExp = it[MATCH]) !== undefined ? !!isRegExp : _cof(it) == 'RegExp');
  };

  // 7.3.20 SpeciesConstructor(O, defaultConstructor)


  var SPECIES$2 = _wks('species');
  var _speciesConstructor = function (O, D) {
    var C = _anObject(O).constructor;
    var S;
    return C === undefined || (S = _anObject(C)[SPECIES$2]) == undefined ? D : _aFunction(S);
  };

  var at = _stringAt(true);

   // `AdvanceStringIndex` abstract operation
  // https://tc39.github.io/ecma262/#sec-advancestringindex
  var _advanceStringIndex = function (S, index, unicode) {
    return index + (unicode ? at(S, index).length : 1);
  };

  var builtinExec = RegExp.prototype.exec;

   // `RegExpExec` abstract operation
  // https://tc39.github.io/ecma262/#sec-regexpexec
  var _regexpExecAbstract = function (R, S) {
    var exec = R.exec;
    if (typeof exec === 'function') {
      var result = exec.call(R, S);
      if (typeof result !== 'object') {
        throw new TypeError('RegExp exec method returned something other than an Object or null');
      }
      return result;
    }
    if (_classof(R) !== 'RegExp') {
      throw new TypeError('RegExp#exec called on incompatible receiver');
    }
    return builtinExec.call(R, S);
  };

  // 21.2.5.3 get RegExp.prototype.flags

  var _flags = function () {
    var that = _anObject(this);
    var result = '';
    if (that.global) result += 'g';
    if (that.ignoreCase) result += 'i';
    if (that.multiline) result += 'm';
    if (that.unicode) result += 'u';
    if (that.sticky) result += 'y';
    return result;
  };

  var nativeExec = RegExp.prototype.exec;
  // This always refers to the native implementation, because the
  // String#replace polyfill uses ./fix-regexp-well-known-symbol-logic.js,
  // which loads this file before patching the method.
  var nativeReplace = String.prototype.replace;

  var patchedExec = nativeExec;

  var LAST_INDEX = 'lastIndex';

  var UPDATES_LAST_INDEX_WRONG = (function () {
    var re1 = /a/,
        re2 = /b*/g;
    nativeExec.call(re1, 'a');
    nativeExec.call(re2, 'a');
    return re1[LAST_INDEX] !== 0 || re2[LAST_INDEX] !== 0;
  })();

  // nonparticipating capturing group, copied from es5-shim's String#split patch.
  var NPCG_INCLUDED = /()??/.exec('')[1] !== undefined;

  var PATCH = UPDATES_LAST_INDEX_WRONG || NPCG_INCLUDED;

  if (PATCH) {
    patchedExec = function exec(str) {
      var re = this;
      var lastIndex, reCopy, match, i;

      if (NPCG_INCLUDED) {
        reCopy = new RegExp('^' + re.source + '$(?!\\s)', _flags.call(re));
      }
      if (UPDATES_LAST_INDEX_WRONG) lastIndex = re[LAST_INDEX];

      match = nativeExec.call(re, str);

      if (UPDATES_LAST_INDEX_WRONG && match) {
        re[LAST_INDEX] = re.global ? match.index + match[0].length : lastIndex;
      }
      if (NPCG_INCLUDED && match && match.length > 1) {
        // Fix browsers whose `exec` methods don't consistently return `undefined`
        // for NPCG, like IE8. NOTE: This doesn' work for /(.?)?/
        // eslint-disable-next-line no-loop-func
        nativeReplace.call(match[0], reCopy, function () {
          for (i = 1; i < arguments.length - 2; i++) {
            if (arguments[i] === undefined) match[i] = undefined;
          }
        });
      }

      return match;
    };
  }

  var _regexpExec = patchedExec;

  _export({
    target: 'RegExp',
    proto: true,
    forced: _regexpExec !== /./.exec
  }, {
    exec: _regexpExec
  });

  var SPECIES$3 = _wks('species');

  var REPLACE_SUPPORTS_NAMED_GROUPS = !_fails(function () {
    // #replace needs built-in support for named groups.
    // #match works fine because it just return the exec results, even if it has
    // a "grops" property.
    var re = /./;
    re.exec = function () {
      var result = [];
      result.groups = { a: '7' };
      return result;
    };
    return ''.replace(re, '$<a>') !== '7';
  });

  var SPLIT_WORKS_WITH_OVERWRITTEN_EXEC = (function () {
    // Chrome 51 has a buggy "split" implementation when RegExp#exec !== nativeExec
    var re = /(?:)/;
    var originalExec = re.exec;
    re.exec = function () { return originalExec.apply(this, arguments); };
    var result = 'ab'.split(re);
    return result.length === 2 && result[0] === 'a' && result[1] === 'b';
  })();

  var _fixReWks = function (KEY, length, exec) {
    var SYMBOL = _wks(KEY);

    var DELEGATES_TO_SYMBOL = !_fails(function () {
      // String methods call symbol-named RegEp methods
      var O = {};
      O[SYMBOL] = function () { return 7; };
      return ''[KEY](O) != 7;
    });

    var DELEGATES_TO_EXEC = DELEGATES_TO_SYMBOL ? !_fails(function () {
      // Symbol-named RegExp methods call .exec
      var execCalled = false;
      var re = /a/;
      re.exec = function () { execCalled = true; return null; };
      if (KEY === 'split') {
        // RegExp[@@split] doesn't call the regex's exec method, but first creates
        // a new one. We need to return the patched regex when creating the new one.
        re.constructor = {};
        re.constructor[SPECIES$3] = function () { return re; };
      }
      re[SYMBOL]('');
      return !execCalled;
    }) : undefined;

    if (
      !DELEGATES_TO_SYMBOL ||
      !DELEGATES_TO_EXEC ||
      (KEY === 'replace' && !REPLACE_SUPPORTS_NAMED_GROUPS) ||
      (KEY === 'split' && !SPLIT_WORKS_WITH_OVERWRITTEN_EXEC)
    ) {
      var nativeRegExpMethod = /./[SYMBOL];
      var fns = exec(
        _defined,
        SYMBOL,
        ''[KEY],
        function maybeCallNative(nativeMethod, regexp, str, arg2, forceStringMethod) {
          if (regexp.exec === _regexpExec) {
            if (DELEGATES_TO_SYMBOL && !forceStringMethod) {
              // The native String method already delegates to @@method (this
              // polyfilled function), leasing to infinite recursion.
              // We avoid it by directly calling the native @@method method.
              return { done: true, value: nativeRegExpMethod.call(regexp, str, arg2) };
            }
            return { done: true, value: nativeMethod.call(str, regexp, arg2) };
          }
          return { done: false };
        }
      );
      var strfn = fns[0];
      var rxfn = fns[1];

      _redefine(String.prototype, KEY, strfn);
      _hide(RegExp.prototype, SYMBOL, length == 2
        // 21.2.5.8 RegExp.prototype[@@replace](string, replaceValue)
        // 21.2.5.11 RegExp.prototype[@@split](string, limit)
        ? function (string, arg) { return rxfn.call(string, this, arg); }
        // 21.2.5.6 RegExp.prototype[@@match](string)
        // 21.2.5.9 RegExp.prototype[@@search](string)
        : function (string) { return rxfn.call(string, this); }
      );
    }
  };

  var $min = Math.min;
  var $push = [].push;
  var $SPLIT = 'split';
  var LENGTH = 'length';
  var LAST_INDEX$1 = 'lastIndex';
  var MAX_UINT32 = 0xffffffff;

  // babel-minify transpiles RegExp('x', 'y') -> /x/y and it causes SyntaxError
  var SUPPORTS_Y = !_fails(function () { RegExp(MAX_UINT32, 'y'); });

  // @@split logic
  _fixReWks('split', 2, function (defined, SPLIT, $split, maybeCallNative) {
    var internalSplit;
    if (
      'abbc'[$SPLIT](/(b)*/)[1] == 'c' ||
      'test'[$SPLIT](/(?:)/, -1)[LENGTH] != 4 ||
      'ab'[$SPLIT](/(?:ab)*/)[LENGTH] != 2 ||
      '.'[$SPLIT](/(.?)(.?)/)[LENGTH] != 4 ||
      '.'[$SPLIT](/()()/)[LENGTH] > 1 ||
      ''[$SPLIT](/.?/)[LENGTH]
    ) {
      // based on es5-shim implementation, need to rework it
      internalSplit = function (separator, limit) {
        var string = String(this);
        if (separator === undefined && limit === 0) return [];
        // If `separator` is not a regex, use native split
        if (!_isRegexp(separator)) return $split.call(string, separator, limit);
        var output = [];
        var flags = (separator.ignoreCase ? 'i' : '') +
                    (separator.multiline ? 'm' : '') +
                    (separator.unicode ? 'u' : '') +
                    (separator.sticky ? 'y' : '');
        var lastLastIndex = 0;
        var splitLimit = limit === undefined ? MAX_UINT32 : limit >>> 0;
        // Make `global` and avoid `lastIndex` issues by working with a copy
        var separatorCopy = new RegExp(separator.source, flags + 'g');
        var match, lastIndex, lastLength;
        while (match = _regexpExec.call(separatorCopy, string)) {
          lastIndex = separatorCopy[LAST_INDEX$1];
          if (lastIndex > lastLastIndex) {
            output.push(string.slice(lastLastIndex, match.index));
            if (match[LENGTH] > 1 && match.index < string[LENGTH]) $push.apply(output, match.slice(1));
            lastLength = match[0][LENGTH];
            lastLastIndex = lastIndex;
            if (output[LENGTH] >= splitLimit) break;
          }
          if (separatorCopy[LAST_INDEX$1] === match.index) separatorCopy[LAST_INDEX$1]++; // Avoid an infinite loop
        }
        if (lastLastIndex === string[LENGTH]) {
          if (lastLength || !separatorCopy.test('')) output.push('');
        } else output.push(string.slice(lastLastIndex));
        return output[LENGTH] > splitLimit ? output.slice(0, splitLimit) : output;
      };
    // Chakra, V8
    } else if ('0'[$SPLIT](undefined, 0)[LENGTH]) {
      internalSplit = function (separator, limit) {
        return separator === undefined && limit === 0 ? [] : $split.call(this, separator, limit);
      };
    } else {
      internalSplit = $split;
    }

    return [
      // `String.prototype.split` method
      // https://tc39.github.io/ecma262/#sec-string.prototype.split
      function split(separator, limit) {
        var O = defined(this);
        var splitter = separator == undefined ? undefined : separator[SPLIT];
        return splitter !== undefined
          ? splitter.call(separator, O, limit)
          : internalSplit.call(String(O), separator, limit);
      },
      // `RegExp.prototype[@@split]` method
      // https://tc39.github.io/ecma262/#sec-regexp.prototype-@@split
      //
      // NOTE: This cannot be properly polyfilled in engines that don't support
      // the 'y' flag.
      function (regexp, limit) {
        var res = maybeCallNative(internalSplit, regexp, this, limit, internalSplit !== $split);
        if (res.done) return res.value;

        var rx = _anObject(regexp);
        var S = String(this);
        var C = _speciesConstructor(rx, RegExp);

        var unicodeMatching = rx.unicode;
        var flags = (rx.ignoreCase ? 'i' : '') +
                    (rx.multiline ? 'm' : '') +
                    (rx.unicode ? 'u' : '') +
                    (SUPPORTS_Y ? 'y' : 'g');

        // ^(? + rx + ) is needed, in combination with some S slicing, to
        // simulate the 'y' flag.
        var splitter = new C(SUPPORTS_Y ? rx : '^(?:' + rx.source + ')', flags);
        var lim = limit === undefined ? MAX_UINT32 : limit >>> 0;
        if (lim === 0) return [];
        if (S.length === 0) return _regexpExecAbstract(splitter, S) === null ? [S] : [];
        var p = 0;
        var q = 0;
        var A = [];
        while (q < S.length) {
          splitter.lastIndex = SUPPORTS_Y ? q : 0;
          var z = _regexpExecAbstract(splitter, SUPPORTS_Y ? S : S.slice(q));
          var e;
          if (
            z === null ||
            (e = $min(_toLength(splitter.lastIndex + (SUPPORTS_Y ? 0 : q)), S.length)) === p
          ) {
            q = _advanceStringIndex(S, q, unicodeMatching);
          } else {
            A.push(S.slice(p, q));
            if (A.length === lim) return A;
            for (var i = 1; i <= z.length - 1; i++) {
              A.push(z[i]);
              if (A.length === lim) return A;
            }
            q = p = e;
          }
        }
        A.push(S.slice(p));
        return A;
      }
    ];
  });

  /**
   * @alias zrender/graphic/Text
   * @extends module:zrender/graphic/Displayable
   * @constructor
   * @param {Object} opts
   */
  var Text = function (opts) { // jshint ignore:line
      Displayable.call(this, opts);
  };

  Text.prototype = {

      constructor: Text,

      type: 'text',

      brush: function (ctx, prevEl) {
          var style = this.style;

          // Optimize, avoid normalize every time.
          this.__dirty && normalizeTextStyle(style);

          // Use props with prefix 'text'.
          style.fill = style.stroke = style.shadowBlur = style.shadowColor =
              style.shadowOffsetX = style.shadowOffsetY = null;

          var text = style.text;
          // Convert to string
          text != null && (text += '');

          // Do not apply style.bind in Text node. Because the real bind job
          // is in textHelper.renderText, and performance of text render should
          // be considered.
          // style.bind(ctx, this, prevEl);

          if (!needDrawText(text, style)) {
              // The current el.style is not applied
              // and should not be used as cache.
              ctx.__attrCachedBy = ContextCachedBy.NONE;
              return;
          }

          this.setTransform(ctx);

          renderText(this, ctx, text, style, null, prevEl);

          this.restoreTransform(ctx);
      },

      getBoundingRect: function () {
          var style = this.style;

          // Optimize, avoid normalize every time.
          this.__dirty && normalizeTextStyle(style);

          if (!this._rect) {
              var text = style.text;
              text != null ? (text += '') : (text = '');

              var rect = getBoundingRect(
                  style.text + '',
                  style.font,
                  style.textAlign,
                  style.textVerticalAlign,
                  style.textPadding,
                  style.textLineHeight,
                  style.rich
              );

              rect.x += style.x || 0;
              rect.y += style.y || 0;

              if (getStroke(style.textStroke, style.textStrokeWidth)) {
                  var w = style.textStrokeWidth;
                  rect.x -= w / 2;
                  rect.y -= w / 2;
                  rect.width += w;
                  rect.height += w;
              }

              this._rect = rect;
          }

          return this._rect;
      }
  };

  inherits(Text, Displayable);

  var GlobalConfig = {
    level: {
      TOOLTIP_LEVEL: 100100,
      //定义点的层级
      POINT_LEVEL: 100020,
      //定义线的层级
      LINK_LEVEl: 100010,
      //定义组的层级
      GROUP_LEVEL: 10,
      //定义动画的层级
      ANIMATION_LEVEL: 100014,
      //
      POINT_ANIMATION_LEVEL: 100015
    },
    //已知mtype类型
    mtype: {
      POINT_CORE_IMG: 'img',
      POINT_CORE_CIR: 'cir',
      POINT_CORE_RECT: 'rect',
      POINT_CORE_LINE: 'line',
      //直线点
      LINK_CORE_TCL: 'two_color_link',
      LINK_CORE_OCL: 'one_color_link',
      GROUP_CORE_IMG: 'img'
    }
  };

  var Tooltip = /*#__PURE__*/function () {
    function Tooltip(config, UTopoView) {
      _classCallCheck(this, Tooltip);

      //topo的视图view对象
      this._view = UTopoView; //tooltip配置

      this._config = config; //记录鼠标事件对象

      this.eventTarget = null; //定时器,延迟触发

      this.timer = null;
      this.domtimer = null; //鼠标移入延迟触发时间

      this.delaytime = config.delaytime ? config.delaytime : 1000; //是否远程加载tooltip数据

      this.remote = config.remote ? config.remote : false;
      this.view = null;
      this.width = null;
      this.padding = null;
      this.init();
    }
    /**
     * 初始化方法,初始化dom,初始化事件
     */


    _createClass(Tooltip, [{
      key: "init",
      value: function init() {
        this.initTooltipView();
        this.initTooltip();
      }
      /***
       * 添加远程方式的tooltip
       * 鼠标:
       * 		移入:显示tooltip
       * 		移出:隐藏tooltip
       * 		按下:不允许显示
       * 		放开:允许显示
       */

    }, {
      key: "initTooltip",
      value: function initTooltip() {
        var _this = this;

        var that = this; //获取view视图对象上的zrender对象

        var _zrender = this._view._zrender; //配置中的formatter对象,如果有配置并且符合要求,则添加事件

        var tooltipFormatter = this._config.formatter;

        if (!tooltipFormatter || !(typeof tooltipFormatter === 'function')) {
          console.warn("no tooltip");
          return;
        } //增加tooltip


        _zrender.on("mousemove", function (e) {
          //为了记录当前触发操作的对象。如果没变 不重复触发
          //不能重复触发
          if (_this.eventTarget === e.target) {
            return;
          } //替换
          //改到timeout方法中替换
          //保证不会在任意地方都显示tooltip数据,只会在有M类的对象上显示tooltip


          if (!e.target || !e.target.model) {
            return;
          } //判断当前鼠标左键是否安下


          if (e.event.button === 1) {
            return;
          } //清空timer


          clearTimeout(_this.timer);
          _this.timer = setTimeout(function () {
            _this.eventTarget = e.target; //获取到view视图上的邮件菜单配置,如果有则需要判断
            //延迟加载的时候还要判断是否可以显示,如果右键菜单存在,并且已经显示,则不能显示tooltip

            var view_contextMenu = that._view._contextMenu;

            if (view_contextMenu) {
              if (view_contextMenu.isShow) {
                return;
              } else {
                //隐藏右键菜单,保证正确
                view_contextMenu.hide();
              }
            } //回传事件触发对象的M类
            //如果有配置的tooltip配置


            var target = e.target.model;
            var msg = "...";

            if (_this.remote) {
              msg = tooltipFormatter(target, _this);
            } else {
              msg = tooltipFormatter(target);
            } //拿到数据开始显示


            var eventX = e.event.layerX;
            var eventY = e.event.layerY; // let eventX = e.event.pageX;
            // let eventY = e.event.pageY;

            if (msg && !(msg === "") && msg !== "undefined") {
              _this.show(msg, eventX, eventY);
            }
          }, _this.delaytime);
        }); //保证拖拽过程中去掉tooltip显示


        _zrender.on('mousedown', function (e) {
          if (e.event.button === 1) {
            clearTimeout(_this.timer);

            _this.hide();
          }
        });

        _zrender.on("mouseout", function (e) {
          _this.eventTarget = null;
          clearTimeout(_this.timer);

          _this.hide();
        });
      }
    }, {
      key: "createDefaultOption",
      value: function createDefaultOption() {
        return {
          x: -1000,
          y: -1000,
          text: '...',
          textFill: '#fff',
          textBoxShadowColor: 'red',
          textBorderWidth: 1,
          textBackgroundColor: 'rgba(50, 50, 50, .8)',
          textBorderColor: 'rgba(50, 50, 50, .8)',
          textPadding: 8,
          textBorderRadius: 5,
          textLineHeight: 18,
          fontSize: 14
        };
      }
      /**
       * 生成tooltip的dom对象
       */

    }, {
      key: "initTooltipView",
      value: function initTooltipView() {
        var _this2 = this;

        var defaultOption = this.createDefaultOption();
        merge(defaultOption, this._config.style, true);
        this.view = new Text({
          zlevel: GlobalConfig.level.TOOLTIP_LEVEL,
          style: defaultOption
        });
        this.width = defaultOption.width;
        this.fontSize = defaultOption.fontSize;
        this.padding = defaultOption.padding; //鼠标事件

        this.view.on('mouseover', function (e) {
          _this2.reshow();
        });
        this.view.on('mouseout', function (e) {
          _this2.hide();
        });

        this._view.add(this.view);
      }
    }, {
      key: "setText",
      value: function setText(text) {
        var newText = this.convertText(text);
        this.view.attr('style', {
          'text': newText
        });
      }
    }, {
      key: "convertText",
      value: function convertText(text) {
        if (this.width == null) {
          return text;
        }

        var width = this.width;
        var textArr = text.split("\n");
        var newTextArr = [];

        for (var i = 0; i < textArr.length; i++) {
          var _text = textArr[i]; //每一行文字计算宽度

          var w = getWidth(_text, null);

          if (w > width) {
            var textArr2 = _text.split("");

            var splitnum = Math.ceil(w / width);
            var size = Math.ceil(textArr2.length / splitnum);

            for (var j = 0; j < splitnum; j++) {
              var t = _text.substring(j * size, (j + 1) * size + 1);

              newTextArr.push(t);
            }
          } else {
            newTextArr.push(_text);
          }
        }

        return newTextArr.join("\n");
      }
      /**
       * 显示方法
       */

    }, {
      key: "show",
      value: function show(text, x, y) {
        this.setText(text);
        this.reshow();
        this.setPosition(x, y);
      }
      /**
       * 鼠标移入到dom上之后恢复显示
       */

    }, {
      key: "reshow",
      value: function reshow() {
        clearInterval(this.domtimer);
        this.view.attr('style', {
          'opacity': 1
        });
        this.view.attr('invisible', false);
      }
      /**
       * 隐藏方法,dom缓慢影藏
       */

    }, {
      key: "hide",
      value: function hide(flag) {
        if (this.view) {
          if (flag) {
            //立即关闭
            this.view.attr('invisible', false);
            this.view.attr('style', {
              x: -1000,
              y: -1000
            });
          } else {
            //清空定时，重启定时
            clearInterval(this.domtimer);
            var that = this;
            var v = 100;
            this.domtimer = setInterval(function () {
              v -= 10;
              that.view.attr('style', {
                'opacity': v / 100
              });

              if (v === 0) {
                that.view.attr('invisible', false);
                that.view.attr('style', {
                  x: -1000,
                  y: -1000
                });
                clearInterval(that.domtimer);
              }
            }, 20);
          }
        }
      }
      /**
       * 设置position位置
       */

    }, {
      key: "setPosition",
      value: function setPosition(x, y) {
        var boundingRect = this.view.getBoundingRect();
        var domHeight = boundingRect.height;
        var domWidth = boundingRect.width;

        if (x + domWidth > this._view.getWidth()) {
          x -= domWidth + 5;
        } else {
          x += 5;
        }

        if (y + domHeight > this._view.getHeight()) {
          y -= domHeight + 5;
        } else {
          y += 5;
        } //太靠下


        if (y < 20) {
          y = 0;
        } //太靠右


        if (x < 20) {
          x = 0;
        }

        this.view.attr('style', {
          x: x,
          y: y
        });
      }
    }]);

    return Tooltip;
  }();

  /**
   * 曲线辅助模块
   * @module zrender/core/curve
   * @author pissang(https://www.github.com/pissang)
   */

  var mathPow = Math.pow;
  var mathSqrt = Math.sqrt;

  var EPSILON$1 = 1e-8;
  var EPSILON_NUMERIC = 1e-4;

  var THREE_SQRT = mathSqrt(3);
  var ONE_THIRD = 1 / 3;

  // 临时变量
  var _v0 = create$1();
  var _v1 = create$1();
  var _v2 = create$1();

  function isAroundZero(val) {
      return val > -EPSILON$1 && val < EPSILON$1;
  }
  function isNotAroundZero$1(val) {
      return val > EPSILON$1 || val < -EPSILON$1;
  }
  /**
   * 计算三次贝塞尔值
   * @memberOf module:zrender/core/curve
   * @param  {number} p0
   * @param  {number} p1
   * @param  {number} p2
   * @param  {number} p3
   * @param  {number} t
   * @return {number}
   */
  function cubicAt(p0, p1, p2, p3, t) {
      var onet = 1 - t;
      return onet * onet * (onet * p0 + 3 * t * p1)
              + t * t * (t * p3 + 3 * onet * p2);
  }

  /**
   * 计算三次贝塞尔导数值
   * @memberOf module:zrender/core/curve
   * @param  {number} p0
   * @param  {number} p1
   * @param  {number} p2
   * @param  {number} p3
   * @param  {number} t
   * @return {number}
   */
  function cubicDerivativeAt(p0, p1, p2, p3, t) {
      var onet = 1 - t;
      return 3 * (
          ((p1 - p0) * onet + 2 * (p2 - p1) * t) * onet
          + (p3 - p2) * t * t
      );
  }

  /**
   * 计算三次贝塞尔方程根，使用盛金公式
   * @memberOf module:zrender/core/curve
   * @param  {number} p0
   * @param  {number} p1
   * @param  {number} p2
   * @param  {number} p3
   * @param  {number} val
   * @param  {Array.<number>} roots
   * @return {number} 有效根数目
   */
  function cubicRootAt(p0, p1, p2, p3, val, roots) {
      // Evaluate roots of cubic functions
      var a = p3 + 3 * (p1 - p2) - p0;
      var b = 3 * (p2 - p1 * 2 + p0);
      var c = 3 * (p1 - p0);
      var d = p0 - val;

      var A = b * b - 3 * a * c;
      var B = b * c - 9 * a * d;
      var C = c * c - 3 * b * d;

      var n = 0;

      if (isAroundZero(A) && isAroundZero(B)) {
          if (isAroundZero(b)) {
              roots[0] = 0;
          }
          else {
              var t1 = -c / b;  //t1, t2, t3, b is not zero
              if (t1 >= 0 && t1 <= 1) {
                  roots[n++] = t1;
              }
          }
      }
      else {
          var disc = B * B - 4 * A * C;

          if (isAroundZero(disc)) {
              var K = B / A;
              var t1 = -b / a + K;  // t1, a is not zero
              var t2 = -K / 2;  // t2, t3
              if (t1 >= 0 && t1 <= 1) {
                  roots[n++] = t1;
              }
              if (t2 >= 0 && t2 <= 1) {
                  roots[n++] = t2;
              }
          }
          else if (disc > 0) {
              var discSqrt = mathSqrt(disc);
              var Y1 = A * b + 1.5 * a * (-B + discSqrt);
              var Y2 = A * b + 1.5 * a * (-B - discSqrt);
              if (Y1 < 0) {
                  Y1 = -mathPow(-Y1, ONE_THIRD);
              }
              else {
                  Y1 = mathPow(Y1, ONE_THIRD);
              }
              if (Y2 < 0) {
                  Y2 = -mathPow(-Y2, ONE_THIRD);
              }
              else {
                  Y2 = mathPow(Y2, ONE_THIRD);
              }
              var t1 = (-b - (Y1 + Y2)) / (3 * a);
              if (t1 >= 0 && t1 <= 1) {
                  roots[n++] = t1;
              }
          }
          else {
              var T = (2 * A * b - 3 * a * B) / (2 * mathSqrt(A * A * A));
              var theta = Math.acos(T) / 3;
              var ASqrt = mathSqrt(A);
              var tmp = Math.cos(theta);

              var t1 = (-b - 2 * ASqrt * tmp) / (3 * a);
              var t2 = (-b + ASqrt * (tmp + THREE_SQRT * Math.sin(theta))) / (3 * a);
              var t3 = (-b + ASqrt * (tmp - THREE_SQRT * Math.sin(theta))) / (3 * a);
              if (t1 >= 0 && t1 <= 1) {
                  roots[n++] = t1;
              }
              if (t2 >= 0 && t2 <= 1) {
                  roots[n++] = t2;
              }
              if (t3 >= 0 && t3 <= 1) {
                  roots[n++] = t3;
              }
          }
      }
      return n;
  }

  /**
   * 计算三次贝塞尔方程极限值的位置
   * @memberOf module:zrender/core/curve
   * @param  {number} p0
   * @param  {number} p1
   * @param  {number} p2
   * @param  {number} p3
   * @param  {Array.<number>} extrema
   * @return {number} 有效数目
   */
  function cubicExtrema(p0, p1, p2, p3, extrema) {
      var b = 6 * p2 - 12 * p1 + 6 * p0;
      var a = 9 * p1 + 3 * p3 - 3 * p0 - 9 * p2;
      var c = 3 * p1 - 3 * p0;

      var n = 0;
      if (isAroundZero(a)) {
          if (isNotAroundZero$1(b)) {
              var t1 = -c / b;
              if (t1 >= 0 && t1 <= 1) {
                  extrema[n++] = t1;
              }
          }
      }
      else {
          var disc = b * b - 4 * a * c;
          if (isAroundZero(disc)) {
              extrema[0] = -b / (2 * a);
          }
          else if (disc > 0) {
              var discSqrt = mathSqrt(disc);
              var t1 = (-b + discSqrt) / (2 * a);
              var t2 = (-b - discSqrt) / (2 * a);
              if (t1 >= 0 && t1 <= 1) {
                  extrema[n++] = t1;
              }
              if (t2 >= 0 && t2 <= 1) {
                  extrema[n++] = t2;
              }
          }
      }
      return n;
  }

  /**
   * 细分三次贝塞尔曲线
   * @memberOf module:zrender/core/curve
   * @param  {number} p0
   * @param  {number} p1
   * @param  {number} p2
   * @param  {number} p3
   * @param  {number} t
   * @param  {Array.<number>} out
   */
  function cubicSubdivide(p0, p1, p2, p3, t, out) {
      var p01 = (p1 - p0) * t + p0;
      var p12 = (p2 - p1) * t + p1;
      var p23 = (p3 - p2) * t + p2;

      var p012 = (p12 - p01) * t + p01;
      var p123 = (p23 - p12) * t + p12;

      var p0123 = (p123 - p012) * t + p012;
      // Seg0
      out[0] = p0;
      out[1] = p01;
      out[2] = p012;
      out[3] = p0123;
      // Seg1
      out[4] = p0123;
      out[5] = p123;
      out[6] = p23;
      out[7] = p3;
  }

  /**
   * 投射点到三次贝塞尔曲线上，返回投射距离。
   * 投射点有可能会有一个或者多个，这里只返回其中距离最短的一个。
   * @param {number} x0
   * @param {number} y0
   * @param {number} x1
   * @param {number} y1
   * @param {number} x2
   * @param {number} y2
   * @param {number} x3
   * @param {number} y3
   * @param {number} x
   * @param {number} y
   * @param {Array.<number>} [out] 投射点
   * @return {number}
   */
  function cubicProjectPoint(
      x0, y0, x1, y1, x2, y2, x3, y3,
      x, y, out
  ) {
      // http://pomax.github.io/bezierinfo/#projections
      var t;
      var interval = 0.005;
      var d = Infinity;
      var prev;
      var next;
      var d1;
      var d2;

      _v0[0] = x;
      _v0[1] = y;

      // 先粗略估计一下可能的最小距离的 t 值
      // PENDING
      for (var _t = 0; _t < 1; _t += 0.05) {
          _v1[0] = cubicAt(x0, x1, x2, x3, _t);
          _v1[1] = cubicAt(y0, y1, y2, y3, _t);
          d1 = distSquare(_v0, _v1);
          if (d1 < d) {
              t = _t;
              d = d1;
          }
      }
      d = Infinity;

      // At most 32 iteration
      for (var i = 0; i < 32; i++) {
          if (interval < EPSILON_NUMERIC) {
              break;
          }
          prev = t - interval;
          next = t + interval;
          // t - interval
          _v1[0] = cubicAt(x0, x1, x2, x3, prev);
          _v1[1] = cubicAt(y0, y1, y2, y3, prev);

          d1 = distSquare(_v1, _v0);

          if (prev >= 0 && d1 < d) {
              t = prev;
              d = d1;
          }
          else {
              // t + interval
              _v2[0] = cubicAt(x0, x1, x2, x3, next);
              _v2[1] = cubicAt(y0, y1, y2, y3, next);
              d2 = distSquare(_v2, _v0);

              if (next <= 1 && d2 < d) {
                  t = next;
                  d = d2;
              }
              else {
                  interval *= 0.5;
              }
          }
      }
      // t
      if (out) {
          out[0] = cubicAt(x0, x1, x2, x3, t);
          out[1] = cubicAt(y0, y1, y2, y3, t);
      }
      // console.log(interval, i);
      return mathSqrt(d);
  }

  /**
   * 计算二次方贝塞尔值
   * @param  {number} p0
   * @param  {number} p1
   * @param  {number} p2
   * @param  {number} t
   * @return {number}
   */
  function quadraticAt(p0, p1, p2, t) {
      var onet = 1 - t;
      return onet * (onet * p0 + 2 * t * p1) + t * t * p2;
  }

  /**
   * 计算二次方贝塞尔导数值
   * @param  {number} p0
   * @param  {number} p1
   * @param  {number} p2
   * @param  {number} t
   * @return {number}
   */
  function quadraticDerivativeAt(p0, p1, p2, t) {
      return 2 * ((1 - t) * (p1 - p0) + t * (p2 - p1));
  }

  /**
   * 计算二次方贝塞尔方程根
   * @param  {number} p0
   * @param  {number} p1
   * @param  {number} p2
   * @param  {number} t
   * @param  {Array.<number>} roots
   * @return {number} 有效根数目
   */
  function quadraticRootAt(p0, p1, p2, val, roots) {
      var a = p0 - 2 * p1 + p2;
      var b = 2 * (p1 - p0);
      var c = p0 - val;

      var n = 0;
      if (isAroundZero(a)) {
          if (isNotAroundZero$1(b)) {
              var t1 = -c / b;
              if (t1 >= 0 && t1 <= 1) {
                  roots[n++] = t1;
              }
          }
      }
      else {
          var disc = b * b - 4 * a * c;
          if (isAroundZero(disc)) {
              var t1 = -b / (2 * a);
              if (t1 >= 0 && t1 <= 1) {
                  roots[n++] = t1;
              }
          }
          else if (disc > 0) {
              var discSqrt = mathSqrt(disc);
              var t1 = (-b + discSqrt) / (2 * a);
              var t2 = (-b - discSqrt) / (2 * a);
              if (t1 >= 0 && t1 <= 1) {
                  roots[n++] = t1;
              }
              if (t2 >= 0 && t2 <= 1) {
                  roots[n++] = t2;
              }
          }
      }
      return n;
  }

  /**
   * 计算二次贝塞尔方程极限值
   * @memberOf module:zrender/core/curve
   * @param  {number} p0
   * @param  {number} p1
   * @param  {number} p2
   * @return {number}
   */
  function quadraticExtremum(p0, p1, p2) {
      var divider = p0 + p2 - 2 * p1;
      if (divider === 0) {
          // p1 is center of p0 and p2
          return 0.5;
      }
      else {
          return (p0 - p1) / divider;
      }
  }

  /**
   * 细分二次贝塞尔曲线
   * @memberOf module:zrender/core/curve
   * @param  {number} p0
   * @param  {number} p1
   * @param  {number} p2
   * @param  {number} t
   * @param  {Array.<number>} out
   */
  function quadraticSubdivide(p0, p1, p2, t, out) {
      var p01 = (p1 - p0) * t + p0;
      var p12 = (p2 - p1) * t + p1;
      var p012 = (p12 - p01) * t + p01;

      // Seg0
      out[0] = p0;
      out[1] = p01;
      out[2] = p012;

      // Seg1
      out[3] = p012;
      out[4] = p12;
      out[5] = p2;
  }

  /**
   * 投射点到二次贝塞尔曲线上，返回投射距离。
   * 投射点有可能会有一个或者多个，这里只返回其中距离最短的一个。
   * @param {number} x0
   * @param {number} y0
   * @param {number} x1
   * @param {number} y1
   * @param {number} x2
   * @param {number} y2
   * @param {number} x
   * @param {number} y
   * @param {Array.<number>} out 投射点
   * @return {number}
   */
  function quadraticProjectPoint(
      x0, y0, x1, y1, x2, y2,
      x, y, out
  ) {
      // http://pomax.github.io/bezierinfo/#projections
      var t;
      var interval = 0.005;
      var d = Infinity;

      _v0[0] = x;
      _v0[1] = y;

      // 先粗略估计一下可能的最小距离的 t 值
      // PENDING
      for (var _t = 0; _t < 1; _t += 0.05) {
          _v1[0] = quadraticAt(x0, x1, x2, _t);
          _v1[1] = quadraticAt(y0, y1, y2, _t);
          var d1 = distSquare(_v0, _v1);
          if (d1 < d) {
              t = _t;
              d = d1;
          }
      }
      d = Infinity;

      // At most 32 iteration
      for (var i = 0; i < 32; i++) {
          if (interval < EPSILON_NUMERIC) {
              break;
          }
          var prev = t - interval;
          var next = t + interval;
          // t - interval
          _v1[0] = quadraticAt(x0, x1, x2, prev);
          _v1[1] = quadraticAt(y0, y1, y2, prev);

          var d1 = distSquare(_v1, _v0);

          if (prev >= 0 && d1 < d) {
              t = prev;
              d = d1;
          }
          else {
              // t + interval
              _v2[0] = quadraticAt(x0, x1, x2, next);
              _v2[1] = quadraticAt(y0, y1, y2, next);
              var d2 = distSquare(_v2, _v0);
              if (next <= 1 && d2 < d) {
                  t = next;
                  d = d2;
              }
              else {
                  interval *= 0.5;
              }
          }
      }
      // t
      if (out) {
          out[0] = quadraticAt(x0, x1, x2, t);
          out[1] = quadraticAt(y0, y1, y2, t);
      }
      // console.log(interval, i);
      return mathSqrt(d);
  }

  /**
   * @author Yi Shen(https://github.com/pissang)
   */

  var mathMin$1 = Math.min;
  var mathMax$1 = Math.max;
  var mathSin = Math.sin;
  var mathCos = Math.cos;
  var PI2 = Math.PI * 2;

  var start = create$1();
  var end = create$1();
  var extremity = create$1();

  /**
   * @memberOf module:zrender/core/bbox
   * @param {number} x0
   * @param {number} y0
   * @param {number} x1
   * @param {number} y1
   * @param {Array.<number>} min
   * @param {Array.<number>} max
   */
  function fromLine(x0, y0, x1, y1, min, max) {
      min[0] = mathMin$1(x0, x1);
      min[1] = mathMin$1(y0, y1);
      max[0] = mathMax$1(x0, x1);
      max[1] = mathMax$1(y0, y1);
  }

  var xDim = [];
  var yDim = [];
  /**
   * 从三阶贝塞尔曲线(p0, p1, p2, p3)中计算出最小包围盒，写入`min`和`max`中
   * @memberOf module:zrender/core/bbox
   * @param {number} x0
   * @param {number} y0
   * @param {number} x1
   * @param {number} y1
   * @param {number} x2
   * @param {number} y2
   * @param {number} x3
   * @param {number} y3
   * @param {Array.<number>} min
   * @param {Array.<number>} max
   */
  function fromCubic(
      x0, y0, x1, y1, x2, y2, x3, y3, min, max
  ) {
      var cubicExtrema$1 = cubicExtrema;
      var cubicAt$1 = cubicAt;
      var i;
      var n = cubicExtrema$1(x0, x1, x2, x3, xDim);
      min[0] = Infinity;
      min[1] = Infinity;
      max[0] = -Infinity;
      max[1] = -Infinity;

      for (i = 0; i < n; i++) {
          var x = cubicAt$1(x0, x1, x2, x3, xDim[i]);
          min[0] = mathMin$1(x, min[0]);
          max[0] = mathMax$1(x, max[0]);
      }
      n = cubicExtrema$1(y0, y1, y2, y3, yDim);
      for (i = 0; i < n; i++) {
          var y = cubicAt$1(y0, y1, y2, y3, yDim[i]);
          min[1] = mathMin$1(y, min[1]);
          max[1] = mathMax$1(y, max[1]);
      }

      min[0] = mathMin$1(x0, min[0]);
      max[0] = mathMax$1(x0, max[0]);
      min[0] = mathMin$1(x3, min[0]);
      max[0] = mathMax$1(x3, max[0]);

      min[1] = mathMin$1(y0, min[1]);
      max[1] = mathMax$1(y0, max[1]);
      min[1] = mathMin$1(y3, min[1]);
      max[1] = mathMax$1(y3, max[1]);
  }

  /**
   * 从二阶贝塞尔曲线(p0, p1, p2)中计算出最小包围盒，写入`min`和`max`中
   * @memberOf module:zrender/core/bbox
   * @param {number} x0
   * @param {number} y0
   * @param {number} x1
   * @param {number} y1
   * @param {number} x2
   * @param {number} y2
   * @param {Array.<number>} min
   * @param {Array.<number>} max
   */
  function fromQuadratic(x0, y0, x1, y1, x2, y2, min, max) {
      var quadraticExtremum$1 = quadraticExtremum;
      var quadraticAt$1 = quadraticAt;
      // Find extremities, where derivative in x dim or y dim is zero
      var tx =
          mathMax$1(
              mathMin$1(quadraticExtremum$1(x0, x1, x2), 1), 0
          );
      var ty =
          mathMax$1(
              mathMin$1(quadraticExtremum$1(y0, y1, y2), 1), 0
          );

      var x = quadraticAt$1(x0, x1, x2, tx);
      var y = quadraticAt$1(y0, y1, y2, ty);

      min[0] = mathMin$1(x0, x2, x);
      min[1] = mathMin$1(y0, y2, y);
      max[0] = mathMax$1(x0, x2, x);
      max[1] = mathMax$1(y0, y2, y);
  }

  /**
   * 从圆弧中计算出最小包围盒，写入`min`和`max`中
   * @method
   * @memberOf module:zrender/core/bbox
   * @param {number} x
   * @param {number} y
   * @param {number} rx
   * @param {number} ry
   * @param {number} startAngle
   * @param {number} endAngle
   * @param {number} anticlockwise
   * @param {Array.<number>} min
   * @param {Array.<number>} max
   */
  function fromArc(
      x, y, rx, ry, startAngle, endAngle, anticlockwise, min, max$1
  ) {
      var vec2Min = min$1;
      var vec2Max = max;

      var diff = Math.abs(startAngle - endAngle);


      if (diff % PI2 < 1e-4 && diff > 1e-4) {
          // Is a circle
          min[0] = x - rx;
          min[1] = y - ry;
          max$1[0] = x + rx;
          max$1[1] = y + ry;
          return;
      }

      start[0] = mathCos(startAngle) * rx + x;
      start[1] = mathSin(startAngle) * ry + y;

      end[0] = mathCos(endAngle) * rx + x;
      end[1] = mathSin(endAngle) * ry + y;

      vec2Min(min, start, end);
      vec2Max(max$1, start, end);

      // Thresh to [0, Math.PI * 2]
      startAngle = startAngle % (PI2);
      if (startAngle < 0) {
          startAngle = startAngle + PI2;
      }
      endAngle = endAngle % (PI2);
      if (endAngle < 0) {
          endAngle = endAngle + PI2;
      }

      if (startAngle > endAngle && !anticlockwise) {
          endAngle += PI2;
      }
      else if (startAngle < endAngle && anticlockwise) {
          startAngle += PI2;
      }
      if (anticlockwise) {
          var tmp = endAngle;
          endAngle = startAngle;
          startAngle = tmp;
      }

      // var number = 0;
      // var step = (anticlockwise ? -Math.PI : Math.PI) / 2;
      for (var angle = 0; angle < endAngle; angle += Math.PI / 2) {
          if (angle > startAngle) {
              extremity[0] = mathCos(angle) * rx + x;
              extremity[1] = mathSin(angle) * ry + y;

              vec2Min(min, extremity, min);
              vec2Max(max$1, extremity, max$1);
          }
      }
  }

  /**
   * Path 代理，可以在`buildPath`中用于替代`ctx`, 会保存每个path操作的命令到pathCommands属性中
   * 可以用于 isInsidePath 判断以及获取boundingRect
   *
   * @module zrender/core/PathProxy
   * @author Yi Shen (http://www.github.com/pissang)
   */

  var CMD = {
      M: 1,
      L: 2,
      C: 3,
      Q: 4,
      A: 5,
      Z: 6,
      // Rect
      R: 7
  };

  // var CMD_MEM_SIZE = {
  //     M: 3,
  //     L: 3,
  //     C: 7,
  //     Q: 5,
  //     A: 9,
  //     R: 5,
  //     Z: 1
  // };

  var min$3 = [];
  var max$2 = [];
  var min2 = [];
  var max2 = [];
  var mathMin$2 = Math.min;
  var mathMax$2 = Math.max;
  var mathCos$1 = Math.cos;
  var mathSin$1 = Math.sin;
  var mathSqrt$1 = Math.sqrt;
  var mathAbs = Math.abs;

  var hasTypedArray = typeof Float32Array !== 'undefined';

  /**
   * @alias module:zrender/core/PathProxy
   * @constructor
   */
  var PathProxy = function (notSaveData) {

      this._saveData = !(notSaveData || false);

      if (this._saveData) {
          /**
           * Path data. Stored as flat array
           * @type {Array.<Object>}
           */
          this.data = [];
      }

      this._ctx = null;
  };

  /**
   * 快速计算Path包围盒（并不是最小包围盒）
   * @return {Object}
   */
  PathProxy.prototype = {

      constructor: PathProxy,

      _xi: 0,
      _yi: 0,

      _x0: 0,
      _y0: 0,
      // Unit x, Unit y. Provide for avoiding drawing that too short line segment
      _ux: 0,
      _uy: 0,

      _len: 0,

      _lineDash: null,

      _dashOffset: 0,

      _dashIdx: 0,

      _dashSum: 0,

      /**
       * @readOnly
       */
      setScale: function (sx, sy) {
          this._ux = mathAbs(1 / devicePixelRatio / sx) || 0;
          this._uy = mathAbs(1 / devicePixelRatio / sy) || 0;
      },

      getContext: function () {
          return this._ctx;
      },

      /**
       * @param  {CanvasRenderingContext2D} ctx
       * @return {module:zrender/core/PathProxy}
       */
      beginPath: function (ctx) {

          this._ctx = ctx;

          ctx && ctx.beginPath();

          ctx && (this.dpr = ctx.dpr);

          // Reset
          if (this._saveData) {
              this._len = 0;
          }

          if (this._lineDash) {
              this._lineDash = null;

              this._dashOffset = 0;
          }

          return this;
      },

      /**
       * @param  {number} x
       * @param  {number} y
       * @return {module:zrender/core/PathProxy}
       */
      moveTo: function (x, y) {
          this.addData(CMD.M, x, y);
          this._ctx && this._ctx.moveTo(x, y);

          // x0, y0, xi, yi 是记录在 _dashedXXXXTo 方法中使用
          // xi, yi 记录当前点, x0, y0 在 closePath 的时候回到起始点。
          // 有可能在 beginPath 之后直接调用 lineTo，这时候 x0, y0 需要
          // 在 lineTo 方法中记录，这里先不考虑这种情况，dashed line 也只在 IE10- 中不支持
          this._x0 = x;
          this._y0 = y;

          this._xi = x;
          this._yi = y;

          return this;
      },

      /**
       * @param  {number} x
       * @param  {number} y
       * @return {module:zrender/core/PathProxy}
       */
      lineTo: function (x, y) {
          var exceedUnit = mathAbs(x - this._xi) > this._ux
              || mathAbs(y - this._yi) > this._uy
              // Force draw the first segment
              || this._len < 5;

          this.addData(CMD.L, x, y);

          if (this._ctx && exceedUnit) {
              this._needsDash() ? this._dashedLineTo(x, y)
                  : this._ctx.lineTo(x, y);
          }
          if (exceedUnit) {
              this._xi = x;
              this._yi = y;
          }

          return this;
      },

      /**
       * @param  {number} x1
       * @param  {number} y1
       * @param  {number} x2
       * @param  {number} y2
       * @param  {number} x3
       * @param  {number} y3
       * @return {module:zrender/core/PathProxy}
       */
      bezierCurveTo: function (x1, y1, x2, y2, x3, y3) {
          this.addData(CMD.C, x1, y1, x2, y2, x3, y3);
          if (this._ctx) {
              this._needsDash() ? this._dashedBezierTo(x1, y1, x2, y2, x3, y3)
                  : this._ctx.bezierCurveTo(x1, y1, x2, y2, x3, y3);
          }
          this._xi = x3;
          this._yi = y3;
          return this;
      },

      /**
       * @param  {number} x1
       * @param  {number} y1
       * @param  {number} x2
       * @param  {number} y2
       * @return {module:zrender/core/PathProxy}
       */
      quadraticCurveTo: function (x1, y1, x2, y2) {
          this.addData(CMD.Q, x1, y1, x2, y2);
          if (this._ctx) {
              this._needsDash() ? this._dashedQuadraticTo(x1, y1, x2, y2)
                  : this._ctx.quadraticCurveTo(x1, y1, x2, y2);
          }
          this._xi = x2;
          this._yi = y2;
          return this;
      },

      /**
       * @param  {number} cx
       * @param  {number} cy
       * @param  {number} r
       * @param  {number} startAngle
       * @param  {number} endAngle
       * @param  {boolean} anticlockwise
       * @return {module:zrender/core/PathProxy}
       */
      arc: function (cx, cy, r, startAngle, endAngle, anticlockwise) {
          this.addData(
              CMD.A, cx, cy, r, r, startAngle, endAngle - startAngle, 0, anticlockwise ? 0 : 1
          );
          this._ctx && this._ctx.arc(cx, cy, r, startAngle, endAngle, anticlockwise);

          this._xi = mathCos$1(endAngle) * r + cx;
          this._yi = mathSin$1(endAngle) * r + cy;
          return this;
      },

      // TODO
      arcTo: function (x1, y1, x2, y2, radius) {
          if (this._ctx) {
              this._ctx.arcTo(x1, y1, x2, y2, radius);
          }
          return this;
      },

      // TODO
      rect: function (x, y, w, h) {
          this._ctx && this._ctx.rect(x, y, w, h);
          this.addData(CMD.R, x, y, w, h);
          return this;
      },

      /**
       * @return {module:zrender/core/PathProxy}
       */
      closePath: function () {
          this.addData(CMD.Z);

          var ctx = this._ctx;
          var x0 = this._x0;
          var y0 = this._y0;
          if (ctx) {
              this._needsDash() && this._dashedLineTo(x0, y0);
              ctx.closePath();
          }

          this._xi = x0;
          this._yi = y0;
          return this;
      },

      /**
       * Context 从外部传入，因为有可能是 rebuildPath 完之后再 fill。
       * stroke 同样
       * @param {CanvasRenderingContext2D} ctx
       * @return {module:zrender/core/PathProxy}
       */
      fill: function (ctx) {
          ctx && ctx.fill();
          this.toStatic();
      },

      /**
       * @param {CanvasRenderingContext2D} ctx
       * @return {module:zrender/core/PathProxy}
       */
      stroke: function (ctx) {
          ctx && ctx.stroke();
          this.toStatic();
      },

      /**
       * 必须在其它绘制命令前调用
       * Must be invoked before all other path drawing methods
       * @return {module:zrender/core/PathProxy}
       */
      setLineDash: function (lineDash) {
          if (lineDash instanceof Array) {
              this._lineDash = lineDash;

              this._dashIdx = 0;

              var lineDashSum = 0;
              for (var i = 0; i < lineDash.length; i++) {
                  lineDashSum += lineDash[i];
              }
              this._dashSum = lineDashSum;
          }
          return this;
      },

      /**
       * 必须在其它绘制命令前调用
       * Must be invoked before all other path drawing methods
       * @return {module:zrender/core/PathProxy}
       */
      setLineDashOffset: function (offset) {
          this._dashOffset = offset;
          return this;
      },

      /**
       *
       * @return {boolean}
       */
      len: function () {
          return this._len;
      },

      /**
       * 直接设置 Path 数据
       */
      setData: function (data) {

          var len = data.length;

          if (!(this.data && this.data.length === len) && hasTypedArray) {
              this.data = new Float32Array(len);
          }

          for (var i = 0; i < len; i++) {
              this.data[i] = data[i];
          }

          this._len = len;
      },

      /**
       * 添加子路径
       * @param {module:zrender/core/PathProxy|Array.<module:zrender/core/PathProxy>} path
       */
      appendPath: function (path) {
          if (!(path instanceof Array)) {
              path = [path];
          }
          var len = path.length;
          var appendSize = 0;
          var offset = this._len;
          for (var i = 0; i < len; i++) {
              appendSize += path[i].len();
          }
          if (hasTypedArray && (this.data instanceof Float32Array)) {
              this.data = new Float32Array(offset + appendSize);
          }
          for (var i = 0; i < len; i++) {
              var appendPathData = path[i].data;
              for (var k = 0; k < appendPathData.length; k++) {
                  this.data[offset++] = appendPathData[k];
              }
          }
          this._len = offset;
      },

      /**
       * 填充 Path 数据。
       * 尽量复用而不申明新的数组。大部分图形重绘的指令数据长度都是不变的。
       */
      addData: function (cmd) {
          if (!this._saveData) {
              return;
          }

          var data = this.data;
          if (this._len + arguments.length > data.length) {
              // 因为之前的数组已经转换成静态的 Float32Array
              // 所以不够用时需要扩展一个新的动态数组
              this._expandData();
              data = this.data;
          }
          for (var i = 0; i < arguments.length; i++) {
              data[this._len++] = arguments[i];
          }

          this._prevCmd = cmd;
      },

      _expandData: function () {
          // Only if data is Float32Array
          if (!(this.data instanceof Array)) {
              var newData = [];
              for (var i = 0; i < this._len; i++) {
                  newData[i] = this.data[i];
              }
              this.data = newData;
          }
      },

      /**
       * If needs js implemented dashed line
       * @return {boolean}
       * @private
       */
      _needsDash: function () {
          return this._lineDash;
      },

      _dashedLineTo: function (x1, y1) {
          var dashSum = this._dashSum;
          var offset = this._dashOffset;
          var lineDash = this._lineDash;
          var ctx = this._ctx;

          var x0 = this._xi;
          var y0 = this._yi;
          var dx = x1 - x0;
          var dy = y1 - y0;
          var dist = mathSqrt$1(dx * dx + dy * dy);
          var x = x0;
          var y = y0;
          var dash;
          var nDash = lineDash.length;
          var idx;
          dx /= dist;
          dy /= dist;

          if (offset < 0) {
              // Convert to positive offset
              offset = dashSum + offset;
          }
          offset %= dashSum;
          x -= offset * dx;
          y -= offset * dy;

          while ((dx > 0 && x <= x1) || (dx < 0 && x >= x1)
          || (dx === 0 && ((dy > 0 && y <= y1) || (dy < 0 && y >= y1)))) {
              idx = this._dashIdx;
              dash = lineDash[idx];
              x += dx * dash;
              y += dy * dash;
              this._dashIdx = (idx + 1) % nDash;
              // Skip positive offset
              if ((dx > 0 && x < x0) || (dx < 0 && x > x0) || (dy > 0 && y < y0) || (dy < 0 && y > y0)) {
                  continue;
              }
              ctx[idx % 2 ? 'moveTo' : 'lineTo'](
                  dx >= 0 ? mathMin$2(x, x1) : mathMax$2(x, x1),
                  dy >= 0 ? mathMin$2(y, y1) : mathMax$2(y, y1)
              );
          }
          // Offset for next lineTo
          dx = x - x1;
          dy = y - y1;
          this._dashOffset = -mathSqrt$1(dx * dx + dy * dy);
      },

      // Not accurate dashed line to
      _dashedBezierTo: function (x1, y1, x2, y2, x3, y3) {
          var dashSum = this._dashSum;
          var offset = this._dashOffset;
          var lineDash = this._lineDash;
          var ctx = this._ctx;

          var x0 = this._xi;
          var y0 = this._yi;
          var t;
          var dx;
          var dy;
          var cubicAt$1 = cubicAt;
          var bezierLen = 0;
          var idx = this._dashIdx;
          var nDash = lineDash.length;

          var x;
          var y;

          var tmpLen = 0;

          if (offset < 0) {
              // Convert to positive offset
              offset = dashSum + offset;
          }
          offset %= dashSum;
          // Bezier approx length
          for (t = 0; t < 1; t += 0.1) {
              dx = cubicAt$1(x0, x1, x2, x3, t + 0.1)
                  - cubicAt$1(x0, x1, x2, x3, t);
              dy = cubicAt$1(y0, y1, y2, y3, t + 0.1)
                  - cubicAt$1(y0, y1, y2, y3, t);
              bezierLen += mathSqrt$1(dx * dx + dy * dy);
          }

          // Find idx after add offset
          for (; idx < nDash; idx++) {
              tmpLen += lineDash[idx];
              if (tmpLen > offset) {
                  break;
              }
          }
          t = (tmpLen - offset) / bezierLen;

          while (t <= 1) {

              x = cubicAt$1(x0, x1, x2, x3, t);
              y = cubicAt$1(y0, y1, y2, y3, t);

              // Use line to approximate dashed bezier
              // Bad result if dash is long
              idx % 2 ? ctx.moveTo(x, y)
                  : ctx.lineTo(x, y);

              t += lineDash[idx] / bezierLen;

              idx = (idx + 1) % nDash;
          }

          // Finish the last segment and calculate the new offset
          (idx % 2 !== 0) && ctx.lineTo(x3, y3);
          dx = x3 - x;
          dy = y3 - y;
          this._dashOffset = -mathSqrt$1(dx * dx + dy * dy);
      },

      _dashedQuadraticTo: function (x1, y1, x2, y2) {
          // Convert quadratic to cubic using degree elevation
          var x3 = x2;
          var y3 = y2;
          x2 = (x2 + 2 * x1) / 3;
          y2 = (y2 + 2 * y1) / 3;
          x1 = (this._xi + 2 * x1) / 3;
          y1 = (this._yi + 2 * y1) / 3;

          this._dashedBezierTo(x1, y1, x2, y2, x3, y3);
      },

      /**
       * 转成静态的 Float32Array 减少堆内存占用
       * Convert dynamic array to static Float32Array
       */
      toStatic: function () {
          var data = this.data;
          if (data instanceof Array) {
              data.length = this._len;
              if (hasTypedArray) {
                  this.data = new Float32Array(data);
              }
          }
      },

      /**
       * @return {module:zrender/core/BoundingRect}
       */
      getBoundingRect: function () {
          min$3[0] = min$3[1] = min2[0] = min2[1] = Number.MAX_VALUE;
          max$2[0] = max$2[1] = max2[0] = max2[1] = -Number.MAX_VALUE;

          var data = this.data;
          var xi = 0;
          var yi = 0;
          var x0 = 0;
          var y0 = 0;

          for (var i = 0; i < data.length;) {
              var cmd = data[i++];

              if (i === 1) {
                  // 如果第一个命令是 L, C, Q
                  // 则 previous point 同绘制命令的第一个 point
                  //
                  // 第一个命令为 Arc 的情况下会在后面特殊处理
                  xi = data[i];
                  yi = data[i + 1];

                  x0 = xi;
                  y0 = yi;
              }

              switch (cmd) {
                  case CMD.M:
                      // moveTo 命令重新创建一个新的 subpath, 并且更新新的起点
                      // 在 closePath 的时候使用
                      x0 = data[i++];
                      y0 = data[i++];
                      xi = x0;
                      yi = y0;
                      min2[0] = x0;
                      min2[1] = y0;
                      max2[0] = x0;
                      max2[1] = y0;
                      break;
                  case CMD.L:
                      fromLine(xi, yi, data[i], data[i + 1], min2, max2);
                      xi = data[i++];
                      yi = data[i++];
                      break;
                  case CMD.C:
                      fromCubic(
                          xi, yi, data[i++], data[i++], data[i++], data[i++], data[i], data[i + 1],
                          min2, max2
                      );
                      xi = data[i++];
                      yi = data[i++];
                      break;
                  case CMD.Q:
                      fromQuadratic(
                          xi, yi, data[i++], data[i++], data[i], data[i + 1],
                          min2, max2
                      );
                      xi = data[i++];
                      yi = data[i++];
                      break;
                  case CMD.A:
                      // TODO Arc 判断的开销比较大
                      var cx = data[i++];
                      var cy = data[i++];
                      var rx = data[i++];
                      var ry = data[i++];
                      var startAngle = data[i++];
                      var endAngle = data[i++] + startAngle;
                      // TODO Arc 旋转
                      i += 1;
                      var anticlockwise = 1 - data[i++];

                      if (i === 1) {
                          // 直接使用 arc 命令
                          // 第一个命令起点还未定义
                          x0 = mathCos$1(startAngle) * rx + cx;
                          y0 = mathSin$1(startAngle) * ry + cy;
                      }

                      fromArc(
                          cx, cy, rx, ry, startAngle, endAngle,
                          anticlockwise, min2, max2
                      );

                      xi = mathCos$1(endAngle) * rx + cx;
                      yi = mathSin$1(endAngle) * ry + cy;
                      break;
                  case CMD.R:
                      x0 = xi = data[i++];
                      y0 = yi = data[i++];
                      var width = data[i++];
                      var height = data[i++];
                      // Use fromLine
                      fromLine(x0, y0, x0 + width, y0 + height, min2, max2);
                      break;
                  case CMD.Z:
                      xi = x0;
                      yi = y0;
                      break;
              }

              // Union
              min$1(min$3, min$3, min2);
              max(max$2, max$2, max2);
          }

          // No data
          if (i === 0) {
              min$3[0] = min$3[1] = max$2[0] = max$2[1] = 0;
          }

          return new BoundingRect(
              min$3[0], min$3[1], max$2[0] - min$3[0], max$2[1] - min$3[1]
          );
      },

      /**
       * Rebuild path from current data
       * Rebuild path will not consider javascript implemented line dash.
       * @param {CanvasRenderingContext2D} ctx
       */
      rebuildPath: function (ctx) {
          var d = this.data;
          var x0, y0;
          var xi, yi;
          var x, y;
          var ux = this._ux;
          var uy = this._uy;
          var len = this._len;
          for (var i = 0; i < len;) {
              var cmd = d[i++];

              if (i === 1) {
                  // 如果第一个命令是 L, C, Q
                  // 则 previous point 同绘制命令的第一个 point
                  //
                  // 第一个命令为 Arc 的情况下会在后面特殊处理
                  xi = d[i];
                  yi = d[i + 1];

                  x0 = xi;
                  y0 = yi;
              }
              switch (cmd) {
                  case CMD.M:
                      x0 = xi = d[i++];
                      y0 = yi = d[i++];
                      ctx.moveTo(xi, yi);
                      break;
                  case CMD.L:
                      x = d[i++];
                      y = d[i++];
                      // Not draw too small seg between
                      if (mathAbs(x - xi) > ux || mathAbs(y - yi) > uy || i === len - 1) {
                          ctx.lineTo(x, y);
                          xi = x;
                          yi = y;
                      }
                      break;
                  case CMD.C:
                      ctx.bezierCurveTo(
                          d[i++], d[i++], d[i++], d[i++], d[i++], d[i++]
                      );
                      xi = d[i - 2];
                      yi = d[i - 1];
                      break;
                  case CMD.Q:
                      ctx.quadraticCurveTo(d[i++], d[i++], d[i++], d[i++]);
                      xi = d[i - 2];
                      yi = d[i - 1];
                      break;
                  case CMD.A:
                      var cx = d[i++];
                      var cy = d[i++];
                      var rx = d[i++];
                      var ry = d[i++];
                      var theta = d[i++];
                      var dTheta = d[i++];
                      var psi = d[i++];
                      var fs = d[i++];
                      var r = (rx > ry) ? rx : ry;
                      var scaleX = (rx > ry) ? 1 : rx / ry;
                      var scaleY = (rx > ry) ? ry / rx : 1;
                      var isEllipse = Math.abs(rx - ry) > 1e-3;
                      var endAngle = theta + dTheta;
                      if (isEllipse) {
                          ctx.translate(cx, cy);
                          ctx.rotate(psi);
                          ctx.scale(scaleX, scaleY);
                          ctx.arc(0, 0, r, theta, endAngle, 1 - fs);
                          ctx.scale(1 / scaleX, 1 / scaleY);
                          ctx.rotate(-psi);
                          ctx.translate(-cx, -cy);
                      }
                      else {
                          ctx.arc(cx, cy, r, theta, endAngle, 1 - fs);
                      }

                      if (i === 1) {
                          // 直接使用 arc 命令
                          // 第一个命令起点还未定义
                          x0 = mathCos$1(theta) * rx + cx;
                          y0 = mathSin$1(theta) * ry + cy;
                      }
                      xi = mathCos$1(endAngle) * rx + cx;
                      yi = mathSin$1(endAngle) * ry + cy;
                      break;
                  case CMD.R:
                      x0 = xi = d[i];
                      y0 = yi = d[i + 1];
                      ctx.rect(d[i++], d[i++], d[i++], d[i++]);
                      break;
                  case CMD.Z:
                      ctx.closePath();
                      xi = x0;
                      yi = y0;
              }
          }
      }
  };

  PathProxy.CMD = CMD;

  /**
   * 线段包含判断
   * @param  {number}  x0
   * @param  {number}  y0
   * @param  {number}  x1
   * @param  {number}  y1
   * @param  {number}  lineWidth
   * @param  {number}  x
   * @param  {number}  y
   * @return {boolean}
   */
  function containStroke(x0, y0, x1, y1, lineWidth, x, y) {
      if (lineWidth === 0) {
          return false;
      }
      var _l = lineWidth;
      var _a = 0;
      var _b = x0;
      // Quick reject
      if (
          (y > y0 + _l && y > y1 + _l)
          || (y < y0 - _l && y < y1 - _l)
          || (x > x0 + _l && x > x1 + _l)
          || (x < x0 - _l && x < x1 - _l)
      ) {
          return false;
      }

      if (x0 !== x1) {
          _a = (y0 - y1) / (x0 - x1);
          _b = (x0 * y1 - x1 * y0) / (x0 - x1);
      }
      else {
          return Math.abs(x - x0) <= _l / 2;
      }
      var tmp = _a * x - y + _b;
      var _s = tmp * tmp / (_a * _a + 1);
      return _s <= _l / 2 * _l / 2;
  }

  /**
   * 三次贝塞尔曲线描边包含判断
   * @param  {number}  x0
   * @param  {number}  y0
   * @param  {number}  x1
   * @param  {number}  y1
   * @param  {number}  x2
   * @param  {number}  y2
   * @param  {number}  x3
   * @param  {number}  y3
   * @param  {number}  lineWidth
   * @param  {number}  x
   * @param  {number}  y
   * @return {boolean}
   */
  function containStroke$1(x0, y0, x1, y1, x2, y2, x3, y3, lineWidth, x, y) {
      if (lineWidth === 0) {
          return false;
      }
      var _l = lineWidth;
      // Quick reject
      if (
          (y > y0 + _l && y > y1 + _l && y > y2 + _l && y > y3 + _l)
          || (y < y0 - _l && y < y1 - _l && y < y2 - _l && y < y3 - _l)
          || (x > x0 + _l && x > x1 + _l && x > x2 + _l && x > x3 + _l)
          || (x < x0 - _l && x < x1 - _l && x < x2 - _l && x < x3 - _l)
      ) {
          return false;
      }
      var d = cubicProjectPoint(
          x0, y0, x1, y1, x2, y2, x3, y3,
          x, y, null
      );
      return d <= _l / 2;
  }

  /**
   * 二次贝塞尔曲线描边包含判断
   * @param  {number}  x0
   * @param  {number}  y0
   * @param  {number}  x1
   * @param  {number}  y1
   * @param  {number}  x2
   * @param  {number}  y2
   * @param  {number}  lineWidth
   * @param  {number}  x
   * @param  {number}  y
   * @return {boolean}
   */
  function containStroke$2(x0, y0, x1, y1, x2, y2, lineWidth, x, y) {
      if (lineWidth === 0) {
          return false;
      }
      var _l = lineWidth;
      // Quick reject
      if (
          (y > y0 + _l && y > y1 + _l && y > y2 + _l)
          || (y < y0 - _l && y < y1 - _l && y < y2 - _l)
          || (x > x0 + _l && x > x1 + _l && x > x2 + _l)
          || (x < x0 - _l && x < x1 - _l && x < x2 - _l)
      ) {
          return false;
      }
      var d = quadraticProjectPoint(
          x0, y0, x1, y1, x2, y2,
          x, y, null
      );
      return d <= _l / 2;
  }

  var PI2$1 = Math.PI * 2;

  function normalizeRadian(angle) {
      angle %= PI2$1;
      if (angle < 0) {
          angle += PI2$1;
      }
      return angle;
  }

  var PI2$2 = Math.PI * 2;

  /**
   * 圆弧描边包含判断
   * @param  {number}  cx
   * @param  {number}  cy
   * @param  {number}  r
   * @param  {number}  startAngle
   * @param  {number}  endAngle
   * @param  {boolean}  anticlockwise
   * @param  {number} lineWidth
   * @param  {number}  x
   * @param  {number}  y
   * @return {Boolean}
   */
  function containStroke$3(
      cx, cy, r, startAngle, endAngle, anticlockwise,
      lineWidth, x, y
  ) {

      if (lineWidth === 0) {
          return false;
      }
      var _l = lineWidth;

      x -= cx;
      y -= cy;
      var d = Math.sqrt(x * x + y * y);

      if ((d - _l > r) || (d + _l < r)) {
          return false;
      }
      if (Math.abs(startAngle - endAngle) % PI2$2 < 1e-4) {
          // Is a circle
          return true;
      }
      if (anticlockwise) {
          var tmp = startAngle;
          startAngle = normalizeRadian(endAngle);
          endAngle = normalizeRadian(tmp);
      }
      else {
          startAngle = normalizeRadian(startAngle);
          endAngle = normalizeRadian(endAngle);
      }
      if (startAngle > endAngle) {
          endAngle += PI2$2;
      }

      var angle = Math.atan2(y, x);
      if (angle < 0) {
          angle += PI2$2;
      }
      return (angle >= startAngle && angle <= endAngle)
          || (angle + PI2$2 >= startAngle && angle + PI2$2 <= endAngle);
  }

  function windingLine(x0, y0, x1, y1, x, y) {
      if ((y > y0 && y > y1) || (y < y0 && y < y1)) {
          return 0;
      }
      // Ignore horizontal line
      if (y1 === y0) {
          return 0;
      }
      var dir = y1 < y0 ? 1 : -1;
      var t = (y - y0) / (y1 - y0);

      // Avoid winding error when intersection point is the connect point of two line of polygon
      if (t === 1 || t === 0) {
          dir = y1 < y0 ? 0.5 : -0.5;
      }

      var x_ = t * (x1 - x0) + x0;

      // If (x, y) on the line, considered as "contain".
      return x_ === x ? Infinity : x_ > x ? dir : 0;
  }

  var CMD$1 = PathProxy.CMD;
  var PI2$3 = Math.PI * 2;

  var EPSILON$2 = 1e-4;

  function isAroundEqual(a, b) {
      return Math.abs(a - b) < EPSILON$2;
  }

  // 临时数组
  var roots = [-1, -1, -1];
  var extrema = [-1, -1];

  function swapExtrema() {
      var tmp = extrema[0];
      extrema[0] = extrema[1];
      extrema[1] = tmp;
  }

  function windingCubic(x0, y0, x1, y1, x2, y2, x3, y3, x, y) {
      // Quick reject
      if (
          (y > y0 && y > y1 && y > y2 && y > y3)
          || (y < y0 && y < y1 && y < y2 && y < y3)
      ) {
          return 0;
      }
      var nRoots = cubicRootAt(y0, y1, y2, y3, y, roots);
      if (nRoots === 0) {
          return 0;
      }
      else {
          var w = 0;
          var nExtrema = -1;
          var y0_;
          var y1_;
          for (var i = 0; i < nRoots; i++) {
              var t = roots[i];

              // Avoid winding error when intersection point is the connect point of two line of polygon
              var unit = (t === 0 || t === 1) ? 0.5 : 1;

              var x_ = cubicAt(x0, x1, x2, x3, t);
              if (x_ < x) { // Quick reject
                  continue;
              }
              if (nExtrema < 0) {
                  nExtrema = cubicExtrema(y0, y1, y2, y3, extrema);
                  if (extrema[1] < extrema[0] && nExtrema > 1) {
                      swapExtrema();
                  }
                  y0_ = cubicAt(y0, y1, y2, y3, extrema[0]);
                  if (nExtrema > 1) {
                      y1_ = cubicAt(y0, y1, y2, y3, extrema[1]);
                  }
              }
              if (nExtrema === 2) {
                  // 分成三段单调函数
                  if (t < extrema[0]) {
                      w += y0_ < y0 ? unit : -unit;
                  }
                  else if (t < extrema[1]) {
                      w += y1_ < y0_ ? unit : -unit;
                  }
                  else {
                      w += y3 < y1_ ? unit : -unit;
                  }
              }
              else {
                  // 分成两段单调函数
                  if (t < extrema[0]) {
                      w += y0_ < y0 ? unit : -unit;
                  }
                  else {
                      w += y3 < y0_ ? unit : -unit;
                  }
              }
          }
          return w;
      }
  }

  function windingQuadratic(x0, y0, x1, y1, x2, y2, x, y) {
      // Quick reject
      if (
          (y > y0 && y > y1 && y > y2)
          || (y < y0 && y < y1 && y < y2)
      ) {
          return 0;
      }
      var nRoots = quadraticRootAt(y0, y1, y2, y, roots);
      if (nRoots === 0) {
          return 0;
      }
      else {
          var t = quadraticExtremum(y0, y1, y2);
          if (t >= 0 && t <= 1) {
              var w = 0;
              var y_ = quadraticAt(y0, y1, y2, t);
              for (var i = 0; i < nRoots; i++) {
                  // Remove one endpoint.
                  var unit = (roots[i] === 0 || roots[i] === 1) ? 0.5 : 1;

                  var x_ = quadraticAt(x0, x1, x2, roots[i]);
                  if (x_ < x) {   // Quick reject
                      continue;
                  }
                  if (roots[i] < t) {
                      w += y_ < y0 ? unit : -unit;
                  }
                  else {
                      w += y2 < y_ ? unit : -unit;
                  }
              }
              return w;
          }
          else {
              // Remove one endpoint.
              var unit = (roots[0] === 0 || roots[0] === 1) ? 0.5 : 1;

              var x_ = quadraticAt(x0, x1, x2, roots[0]);
              if (x_ < x) {   // Quick reject
                  return 0;
              }
              return y2 < y0 ? unit : -unit;
          }
      }
  }

  // TODO
  // Arc 旋转
  function windingArc(
      cx, cy, r, startAngle, endAngle, anticlockwise, x, y
  ) {
      y -= cy;
      if (y > r || y < -r) {
          return 0;
      }
      var tmp = Math.sqrt(r * r - y * y);
      roots[0] = -tmp;
      roots[1] = tmp;

      var diff = Math.abs(startAngle - endAngle);
      if (diff < 1e-4) {
          return 0;
      }
      if (diff % PI2$3 < 1e-4) {
          // Is a circle
          startAngle = 0;
          endAngle = PI2$3;
          var dir = anticlockwise ? 1 : -1;
          if (x >= roots[0] + cx && x <= roots[1] + cx) {
              return dir;
          }
          else {
              return 0;
          }
      }

      if (anticlockwise) {
          var tmp = startAngle;
          startAngle = normalizeRadian(endAngle);
          endAngle = normalizeRadian(tmp);
      }
      else {
          startAngle = normalizeRadian(startAngle);
          endAngle = normalizeRadian(endAngle);
      }
      if (startAngle > endAngle) {
          endAngle += PI2$3;
      }

      var w = 0;
      for (var i = 0; i < 2; i++) {
          var x_ = roots[i];
          if (x_ + cx > x) {
              var angle = Math.atan2(y, x_);
              var dir = anticlockwise ? 1 : -1;
              if (angle < 0) {
                  angle = PI2$3 + angle;
              }
              if (
                  (angle >= startAngle && angle <= endAngle)
                  || (angle + PI2$3 >= startAngle && angle + PI2$3 <= endAngle)
              ) {
                  if (angle > Math.PI / 2 && angle < Math.PI * 1.5) {
                      dir = -dir;
                  }
                  w += dir;
              }
          }
      }
      return w;
  }

  function containPath(data, lineWidth, isStroke, x, y) {
      var w = 0;
      var xi = 0;
      var yi = 0;
      var x0 = 0;
      var y0 = 0;

      for (var i = 0; i < data.length;) {
          var cmd = data[i++];
          // Begin a new subpath
          if (cmd === CMD$1.M && i > 1) {
              // Close previous subpath
              if (!isStroke) {
                  w += windingLine(xi, yi, x0, y0, x, y);
              }
              // 如果被任何一个 subpath 包含
              // if (w !== 0) {
              //     return true;
              // }
          }

          if (i === 1) {
              // 如果第一个命令是 L, C, Q
              // 则 previous point 同绘制命令的第一个 point
              //
              // 第一个命令为 Arc 的情况下会在后面特殊处理
              xi = data[i];
              yi = data[i + 1];

              x0 = xi;
              y0 = yi;
          }

          switch (cmd) {
              case CMD$1.M:
                  // moveTo 命令重新创建一个新的 subpath, 并且更新新的起点
                  // 在 closePath 的时候使用
                  x0 = data[i++];
                  y0 = data[i++];
                  xi = x0;
                  yi = y0;
                  break;
              case CMD$1.L:
                  if (isStroke) {
                      if (containStroke(xi, yi, data[i], data[i + 1], lineWidth, x, y)) {
                          return true;
                      }
                  }
                  else {
                      // NOTE 在第一个命令为 L, C, Q 的时候会计算出 NaN
                      w += windingLine(xi, yi, data[i], data[i + 1], x, y) || 0;
                  }
                  xi = data[i++];
                  yi = data[i++];
                  break;
              case CMD$1.C:
                  if (isStroke) {
                      if (containStroke$1(xi, yi,
                          data[i++], data[i++], data[i++], data[i++], data[i], data[i + 1],
                          lineWidth, x, y
                      )) {
                          return true;
                      }
                  }
                  else {
                      w += windingCubic(
                          xi, yi,
                          data[i++], data[i++], data[i++], data[i++], data[i], data[i + 1],
                          x, y
                      ) || 0;
                  }
                  xi = data[i++];
                  yi = data[i++];
                  break;
              case CMD$1.Q:
                  if (isStroke) {
                      if (containStroke$2(xi, yi,
                          data[i++], data[i++], data[i], data[i + 1],
                          lineWidth, x, y
                      )) {
                          return true;
                      }
                  }
                  else {
                      w += windingQuadratic(
                          xi, yi,
                          data[i++], data[i++], data[i], data[i + 1],
                          x, y
                      ) || 0;
                  }
                  xi = data[i++];
                  yi = data[i++];
                  break;
              case CMD$1.A:
                  // TODO Arc 判断的开销比较大
                  var cx = data[i++];
                  var cy = data[i++];
                  var rx = data[i++];
                  var ry = data[i++];
                  var theta = data[i++];
                  var dTheta = data[i++];
                  // TODO Arc 旋转
                  i += 1;
                  var anticlockwise = 1 - data[i++];
                  var x1 = Math.cos(theta) * rx + cx;
                  var y1 = Math.sin(theta) * ry + cy;
                  // 不是直接使用 arc 命令
                  if (i > 1) {
                      w += windingLine(xi, yi, x1, y1, x, y);
                  }
                  else {
                      // 第一个命令起点还未定义
                      x0 = x1;
                      y0 = y1;
                  }
                  // zr 使用scale来模拟椭圆, 这里也对x做一定的缩放
                  var _x = (x - cx) * ry / rx + cx;
                  if (isStroke) {
                      if (containStroke$3(
                          cx, cy, ry, theta, theta + dTheta, anticlockwise,
                          lineWidth, _x, y
                      )) {
                          return true;
                      }
                  }
                  else {
                      w += windingArc(
                          cx, cy, ry, theta, theta + dTheta, anticlockwise,
                          _x, y
                      );
                  }
                  xi = Math.cos(theta + dTheta) * rx + cx;
                  yi = Math.sin(theta + dTheta) * ry + cy;
                  break;
              case CMD$1.R:
                  x0 = xi = data[i++];
                  y0 = yi = data[i++];
                  var width = data[i++];
                  var height = data[i++];
                  var x1 = x0 + width;
                  var y1 = y0 + height;
                  if (isStroke) {
                      if (containStroke(x0, y0, x1, y0, lineWidth, x, y)
                          || containStroke(x1, y0, x1, y1, lineWidth, x, y)
                          || containStroke(x1, y1, x0, y1, lineWidth, x, y)
                          || containStroke(x0, y1, x0, y0, lineWidth, x, y)
                      ) {
                          return true;
                      }
                  }
                  else {
                      // FIXME Clockwise ?
                      w += windingLine(x1, y0, x1, y1, x, y);
                      w += windingLine(x0, y1, x0, y0, x, y);
                  }
                  break;
              case CMD$1.Z:
                  if (isStroke) {
                      if (containStroke(
                          xi, yi, x0, y0, lineWidth, x, y
                      )) {
                          return true;
                      }
                  }
                  else {
                      // Close a subpath
                      w += windingLine(xi, yi, x0, y0, x, y);
                      // 如果被任何一个 subpath 包含
                      // FIXME subpaths may overlap
                      // if (w !== 0) {
                      //     return true;
                      // }
                  }
                  xi = x0;
                  yi = y0;
                  break;
          }
      }
      if (!isStroke && !isAroundEqual(yi, y0)) {
          w += windingLine(xi, yi, x0, y0, x, y) || 0;
      }
      return w !== 0;
  }

  function contain(pathData, x, y) {
      return containPath(pathData, 0, false, x, y);
  }

  function containStroke$4(pathData, lineWidth, x, y) {
      return containPath(pathData, lineWidth, true, x, y);
  }

  var getCanvasPattern = Pattern.prototype.getCanvasPattern;

  var abs = Math.abs;

  var pathProxyForDraw = new PathProxy(true);
  /**
   * @alias module:zrender/graphic/Path
   * @extends module:zrender/graphic/Displayable
   * @constructor
   * @param {Object} opts
   */
  function Path(opts) {
      Displayable.call(this, opts);

      /**
       * @type {module:zrender/core/PathProxy}
       * @readOnly
       */
      this.path = null;
  }

  Path.prototype = {

      constructor: Path,

      type: 'path',

      __dirtyPath: true,

      strokeContainThreshold: 5,

      /**
       * See `module:zrender/src/graphic/helper/subPixelOptimize`.
       * @type {boolean}
       */
      subPixelOptimize: false,

      brush: function (ctx, prevEl) {
          var style = this.style;
          var path = this.path || pathProxyForDraw;
          var hasStroke = style.hasStroke();
          var hasFill = style.hasFill();
          var fill = style.fill;
          var stroke = style.stroke;
          var hasFillGradient = hasFill && !!(fill.colorStops);
          var hasStrokeGradient = hasStroke && !!(stroke.colorStops);
          var hasFillPattern = hasFill && !!(fill.image);
          var hasStrokePattern = hasStroke && !!(stroke.image);

          style.bind(ctx, this, prevEl);
          this.setTransform(ctx);

          if (this.__dirty) {
              var rect;
              // Update gradient because bounding rect may changed
              if (hasFillGradient) {
                  rect = rect || this.getBoundingRect();
                  this._fillGradient = style.getGradient(ctx, fill, rect);
              }
              if (hasStrokeGradient) {
                  rect = rect || this.getBoundingRect();
                  this._strokeGradient = style.getGradient(ctx, stroke, rect);
              }
          }
          // Use the gradient or pattern
          if (hasFillGradient) {
              // PENDING If may have affect the state
              ctx.fillStyle = this._fillGradient;
          }
          else if (hasFillPattern) {
              ctx.fillStyle = getCanvasPattern.call(fill, ctx);
          }
          if (hasStrokeGradient) {
              ctx.strokeStyle = this._strokeGradient;
          }
          else if (hasStrokePattern) {
              ctx.strokeStyle = getCanvasPattern.call(stroke, ctx);
          }

          var lineDash = style.lineDash;
          var lineDashOffset = style.lineDashOffset;

          var ctxLineDash = !!ctx.setLineDash;

          // Update path sx, sy
          var scale = this.getGlobalScale();
          path.setScale(scale[0], scale[1]);

          // Proxy context
          // Rebuild path in following 2 cases
          // 1. Path is dirty
          // 2. Path needs javascript implemented lineDash stroking.
          //    In this case, lineDash information will not be saved in PathProxy
          if (this.__dirtyPath
              || (lineDash && !ctxLineDash && hasStroke)
          ) {
              path.beginPath(ctx);

              // Setting line dash before build path
              if (lineDash && !ctxLineDash) {
                  path.setLineDash(lineDash);
                  path.setLineDashOffset(lineDashOffset);
              }

              this.buildPath(path, this.shape, false);

              // Clear path dirty flag
              if (this.path) {
                  this.__dirtyPath = false;
              }
          }
          else {
              // Replay path building
              ctx.beginPath();
              this.path.rebuildPath(ctx);
          }

          if (hasFill) {
              if (style.fillOpacity != null) {
                  var originalGlobalAlpha = ctx.globalAlpha;
                  ctx.globalAlpha = style.fillOpacity * style.opacity;
                  path.fill(ctx);
                  ctx.globalAlpha = originalGlobalAlpha;
              }
              else {
                  path.fill(ctx);
              }
          }

          if (lineDash && ctxLineDash) {
              ctx.setLineDash(lineDash);
              ctx.lineDashOffset = lineDashOffset;
          }

          if (hasStroke) {
              if (style.strokeOpacity != null) {
                  var originalGlobalAlpha = ctx.globalAlpha;
                  ctx.globalAlpha = style.strokeOpacity * style.opacity;
                  path.stroke(ctx);
                  ctx.globalAlpha = originalGlobalAlpha;
              }
              else {
                  path.stroke(ctx);
              }
          }

          if (lineDash && ctxLineDash) {
              // PENDING
              // Remove lineDash
              ctx.setLineDash([]);
          }

          // Draw rect text
          if (style.text != null) {
              // Only restore transform when needs draw text.
              this.restoreTransform(ctx);
              this.drawRectText(ctx, this.getBoundingRect());
          }
      },

      // When bundling path, some shape may decide if use moveTo to begin a new subpath or closePath
      // Like in circle
      buildPath: function (ctx, shapeCfg, inBundle) {},

      createPathProxy: function () {
          this.path = new PathProxy();
      },

      getBoundingRect: function () {
          var rect = this._rect;
          var style = this.style;
          var needsUpdateRect = !rect;
          if (needsUpdateRect) {
              var path = this.path;
              if (!path) {
                  // Create path on demand.
                  path = this.path = new PathProxy();
              }
              if (this.__dirtyPath) {
                  path.beginPath();
                  this.buildPath(path, this.shape, false);
              }
              rect = path.getBoundingRect();
          }
          this._rect = rect;

          if (style.hasStroke()) {
              // Needs update rect with stroke lineWidth when
              // 1. Element changes scale or lineWidth
              // 2. Shape is changed
              var rectWithStroke = this._rectWithStroke || (this._rectWithStroke = rect.clone());
              if (this.__dirty || needsUpdateRect) {
                  rectWithStroke.copy(rect);
                  // FIXME Must after updateTransform
                  var w = style.lineWidth;
                  // PENDING, Min line width is needed when line is horizontal or vertical
                  var lineScale = style.strokeNoScale ? this.getLineScale() : 1;

                  // Only add extra hover lineWidth when there are no fill
                  if (!style.hasFill()) {
                      w = Math.max(w, this.strokeContainThreshold || 4);
                  }
                  // Consider line width
                  // Line scale can't be 0;
                  if (lineScale > 1e-10) {
                      rectWithStroke.width += w / lineScale;
                      rectWithStroke.height += w / lineScale;
                      rectWithStroke.x -= w / lineScale / 2;
                      rectWithStroke.y -= w / lineScale / 2;
                  }
              }

              // Return rect with stroke
              return rectWithStroke;
          }

          return rect;
      },

      contain: function (x, y) {
          var localPos = this.transformCoordToLocal(x, y);
          var rect = this.getBoundingRect();
          var style = this.style;
          x = localPos[0];
          y = localPos[1];

          if (rect.contain(x, y)) {
              var pathData = this.path.data;
              if (style.hasStroke()) {
                  var lineWidth = style.lineWidth;
                  var lineScale = style.strokeNoScale ? this.getLineScale() : 1;
                  // Line scale can't be 0;
                  if (lineScale > 1e-10) {
                      // Only add extra hover lineWidth when there are no fill
                      if (!style.hasFill()) {
                          lineWidth = Math.max(lineWidth, this.strokeContainThreshold);
                      }
                      if (containStroke$4(
                          pathData, lineWidth / lineScale, x, y
                      )) {
                          return true;
                      }
                  }
              }
              if (style.hasFill()) {
                  return contain(pathData, x, y);
              }
          }
          return false;
      },

      /**
       * @param  {boolean} dirtyPath
       */
      dirty: function (dirtyPath) {
          if (dirtyPath == null) {
              dirtyPath = true;
          }
          // Only mark dirty, not mark clean
          if (dirtyPath) {
              this.__dirtyPath = dirtyPath;
              this._rect = null;
          }

          this.__dirty = this.__dirtyText = true;

          this.__zr && this.__zr.refresh();

          // Used as a clipping path
          if (this.__clipTarget) {
              this.__clipTarget.dirty();
          }
      },

      /**
       * Alias for animate('shape')
       * @param {boolean} loop
       */
      animateShape: function (loop) {
          return this.animate('shape', loop);
      },

      // Overwrite attrKV
      attrKV: function (key, value) {
          // FIXME
          if (key === 'shape') {
              this.setShape(value);
              this.__dirtyPath = true;
              this._rect = null;
          }
          else {
              Displayable.prototype.attrKV.call(this, key, value);
          }
      },

      /**
       * @param {Object|string} key
       * @param {*} value
       */
      setShape: function (key, value) {
          var shape = this.shape;
          // Path from string may not have shape
          if (shape) {
              if (isObject(key)) {
                  for (var name in key) {
                      if (key.hasOwnProperty(name)) {
                          shape[name] = key[name];
                      }
                  }
              }
              else {
                  shape[key] = value;
              }
              this.dirty(true);
          }
          return this;
      },

      getLineScale: function () {
          var m = this.transform;
          // Get the line scale.
          // Determinant of `m` means how much the area is enlarged by the
          // transformation. So its square root can be used as a scale factor
          // for width.
          return m && abs(m[0] - 1) > 1e-10 && abs(m[3] - 1) > 1e-10
              ? Math.sqrt(abs(m[0] * m[3] - m[2] * m[1]))
              : 1;
      }
  };

  /**
   * 扩展一个 Path element, 比如星形，圆等。
   * Extend a path element
   * @param {Object} props
   * @param {string} props.type Path type
   * @param {Function} props.init Initialize
   * @param {Function} props.buildPath Overwrite buildPath method
   * @param {Object} [props.style] Extended default style config
   * @param {Object} [props.shape] Extended default shape config
   */
  Path.extend = function (defaults) {
      var Sub = function (opts) {
          Path.call(this, opts);

          if (defaults.style) {
              // Extend default style
              this.style.extendFrom(defaults.style, false);
          }

          // Extend default shape
          var defaultShape = defaults.shape;
          if (defaultShape) {
              this.shape = this.shape || {};
              var thisShape = this.shape;
              for (var name in defaultShape) {
                  if (
                      !thisShape.hasOwnProperty(name)
                      && defaultShape.hasOwnProperty(name)
                  ) {
                      thisShape[name] = defaultShape[name];
                  }
              }
          }

          defaults.init && defaults.init.call(this, opts);
      };

      inherits(Sub, Path);

      // FIXME 不能 extend position, rotation 等引用对象
      for (var name in defaults) {
          // Extending prototype values and methods
          if (name !== 'style' && name !== 'shape') {
              Sub.prototype[name] = defaults[name];
          }
      }

      return Sub;
  };

  inherits(Path, Displayable);

  var CMD$2 = PathProxy.CMD;

  var points = [[], [], []];
  var mathSqrt$2 = Math.sqrt;
  var mathAtan2 = Math.atan2;

  function transformPath (path, m) {
      var data = path.data;
      var cmd;
      var nPoint;
      var i;
      var j;
      var k;
      var p;

      var M = CMD$2.M;
      var C = CMD$2.C;
      var L = CMD$2.L;
      var R = CMD$2.R;
      var A = CMD$2.A;
      var Q = CMD$2.Q;

      for (i = 0, j = 0; i < data.length;) {
          cmd = data[i++];
          j = i;
          nPoint = 0;

          switch (cmd) {
              case M:
                  nPoint = 1;
                  break;
              case L:
                  nPoint = 1;
                  break;
              case C:
                  nPoint = 3;
                  break;
              case Q:
                  nPoint = 2;
                  break;
              case A:
                  var x = m[4];
                  var y = m[5];
                  var sx = mathSqrt$2(m[0] * m[0] + m[1] * m[1]);
                  var sy = mathSqrt$2(m[2] * m[2] + m[3] * m[3]);
                  var angle = mathAtan2(-m[1] / sy, m[0] / sx);
                  // cx
                  data[i] *= sx;
                  data[i++] += x;
                  // cy
                  data[i] *= sy;
                  data[i++] += y;
                  // Scale rx and ry
                  // FIXME Assume psi is 0 here
                  data[i++] *= sx;
                  data[i++] *= sy;

                  // Start angle
                  data[i++] += angle;
                  // end angle
                  data[i++] += angle;
                  // FIXME psi
                  i += 2;
                  j = i;
                  break;
              case R:
                  // x0, y0
                  p[0] = data[i++];
                  p[1] = data[i++];
                  applyTransform(p, p, m);
                  data[j++] = p[0];
                  data[j++] = p[1];
                  // x1, y1
                  p[0] += data[i++];
                  p[1] += data[i++];
                  applyTransform(p, p, m);
                  data[j++] = p[0];
                  data[j++] = p[1];
          }

          for (k = 0; k < nPoint; k++) {
              var p = points[k];
              p[0] = data[i++];
              p[1] = data[i++];

              applyTransform(p, p, m);
              // Write back
              data[j++] = p[0];
              data[j++] = p[1];
          }
      }
  }

  // command chars
  // var cc = [
  //     'm', 'M', 'l', 'L', 'v', 'V', 'h', 'H', 'z', 'Z',
  //     'c', 'C', 'q', 'Q', 't', 'T', 's', 'S', 'a', 'A'
  // ];

  var mathSqrt$3 = Math.sqrt;
  var mathSin$2 = Math.sin;
  var mathCos$2 = Math.cos;
  var PI = Math.PI;

  var vMag = function (v) {
      return Math.sqrt(v[0] * v[0] + v[1] * v[1]);
  };
  var vRatio = function (u, v) {
      return (u[0] * v[0] + u[1] * v[1]) / (vMag(u) * vMag(v));
  };
  var vAngle = function (u, v) {
      return (u[0] * v[1] < u[1] * v[0] ? -1 : 1)
              * Math.acos(vRatio(u, v));
  };

  function processArc(x1, y1, x2, y2, fa, fs, rx, ry, psiDeg, cmd, path) {
      var psi = psiDeg * (PI / 180.0);
      var xp = mathCos$2(psi) * (x1 - x2) / 2.0
                  + mathSin$2(psi) * (y1 - y2) / 2.0;
      var yp = -1 * mathSin$2(psi) * (x1 - x2) / 2.0
                  + mathCos$2(psi) * (y1 - y2) / 2.0;

      var lambda = (xp * xp) / (rx * rx) + (yp * yp) / (ry * ry);

      if (lambda > 1) {
          rx *= mathSqrt$3(lambda);
          ry *= mathSqrt$3(lambda);
      }

      var f = (fa === fs ? -1 : 1)
          * mathSqrt$3((((rx * rx) * (ry * ry))
                  - ((rx * rx) * (yp * yp))
                  - ((ry * ry) * (xp * xp))) / ((rx * rx) * (yp * yp)
                  + (ry * ry) * (xp * xp))
              ) || 0;

      var cxp = f * rx * yp / ry;
      var cyp = f * -ry * xp / rx;

      var cx = (x1 + x2) / 2.0
                  + mathCos$2(psi) * cxp
                  - mathSin$2(psi) * cyp;
      var cy = (y1 + y2) / 2.0
              + mathSin$2(psi) * cxp
              + mathCos$2(psi) * cyp;

      var theta = vAngle([ 1, 0 ], [ (xp - cxp) / rx, (yp - cyp) / ry ]);
      var u = [ (xp - cxp) / rx, (yp - cyp) / ry ];
      var v = [ (-1 * xp - cxp) / rx, (-1 * yp - cyp) / ry ];
      var dTheta = vAngle(u, v);

      if (vRatio(u, v) <= -1) {
          dTheta = PI;
      }
      if (vRatio(u, v) >= 1) {
          dTheta = 0;
      }
      if (fs === 0 && dTheta > 0) {
          dTheta = dTheta - 2 * PI;
      }
      if (fs === 1 && dTheta < 0) {
          dTheta = dTheta + 2 * PI;
      }

      path.addData(cmd, cx, cy, rx, ry, theta, dTheta, psi, fs);
  }


  var commandReg = /([mlvhzcqtsa])([^mlvhzcqtsa]*)/ig;
  // Consider case:
  // (1) delimiter can be comma or space, where continuous commas
  // or spaces should be seen as one comma.
  // (2) value can be like:
  // '2e-4', 'l.5.9' (ignore 0), 'M-10-10', 'l-2.43e-1,34.9983',
  // 'l-.5E1,54', '121-23-44-11' (no delimiter)
  var numberReg = /-?([0-9]*\.)?[0-9]+([eE]-?[0-9]+)?/g;
  // var valueSplitReg = /[\s,]+/;

  function createPathProxyFromString(data) {
      if (!data) {
          return new PathProxy();
      }

      // var data = data.replace(/-/g, ' -')
      //     .replace(/  /g, ' ')
      //     .replace(/ /g, ',')
      //     .replace(/,,/g, ',');

      // var n;
      // create pipes so that we can split the data
      // for (n = 0; n < cc.length; n++) {
      //     cs = cs.replace(new RegExp(cc[n], 'g'), '|' + cc[n]);
      // }

      // data = data.replace(/-/g, ',-');

      // create array
      // var arr = cs.split('|');
      // init context point
      var cpx = 0;
      var cpy = 0;
      var subpathX = cpx;
      var subpathY = cpy;
      var prevCmd;

      var path = new PathProxy();
      var CMD = PathProxy.CMD;

      // commandReg.lastIndex = 0;
      // var cmdResult;
      // while ((cmdResult = commandReg.exec(data)) != null) {
      //     var cmdStr = cmdResult[1];
      //     var cmdContent = cmdResult[2];

      var cmdList = data.match(commandReg);
      for (var l = 0; l < cmdList.length; l++) {
          var cmdText = cmdList[l];
          var cmdStr = cmdText.charAt(0);

          var cmd;

          // String#split is faster a little bit than String#replace or RegExp#exec.
          // var p = cmdContent.split(valueSplitReg);
          // var pLen = 0;
          // for (var i = 0; i < p.length; i++) {
          //     // '' and other invalid str => NaN
          //     var val = parseFloat(p[i]);
          //     !isNaN(val) && (p[pLen++] = val);
          // }

          var p = cmdText.match(numberReg) || [];
          var pLen = p.length;
          for (var i = 0; i < pLen; i++) {
              p[i] = parseFloat(p[i]);
          }

          var off = 0;
          while (off < pLen) {
              var ctlPtx;
              var ctlPty;

              var rx;
              var ry;
              var psi;
              var fa;
              var fs;

              var x1 = cpx;
              var y1 = cpy;

              // convert l, H, h, V, and v to L
              switch (cmdStr) {
                  case 'l':
                      cpx += p[off++];
                      cpy += p[off++];
                      cmd = CMD.L;
                      path.addData(cmd, cpx, cpy);
                      break;
                  case 'L':
                      cpx = p[off++];
                      cpy = p[off++];
                      cmd = CMD.L;
                      path.addData(cmd, cpx, cpy);
                      break;
                  case 'm':
                      cpx += p[off++];
                      cpy += p[off++];
                      cmd = CMD.M;
                      path.addData(cmd, cpx, cpy);
                      subpathX = cpx;
                      subpathY = cpy;
                      cmdStr = 'l';
                      break;
                  case 'M':
                      cpx = p[off++];
                      cpy = p[off++];
                      cmd = CMD.M;
                      path.addData(cmd, cpx, cpy);
                      subpathX = cpx;
                      subpathY = cpy;
                      cmdStr = 'L';
                      break;
                  case 'h':
                      cpx += p[off++];
                      cmd = CMD.L;
                      path.addData(cmd, cpx, cpy);
                      break;
                  case 'H':
                      cpx = p[off++];
                      cmd = CMD.L;
                      path.addData(cmd, cpx, cpy);
                      break;
                  case 'v':
                      cpy += p[off++];
                      cmd = CMD.L;
                      path.addData(cmd, cpx, cpy);
                      break;
                  case 'V':
                      cpy = p[off++];
                      cmd = CMD.L;
                      path.addData(cmd, cpx, cpy);
                      break;
                  case 'C':
                      cmd = CMD.C;
                      path.addData(
                          cmd, p[off++], p[off++], p[off++], p[off++], p[off++], p[off++]
                      );
                      cpx = p[off - 2];
                      cpy = p[off - 1];
                      break;
                  case 'c':
                      cmd = CMD.C;
                      path.addData(
                          cmd,
                          p[off++] + cpx, p[off++] + cpy,
                          p[off++] + cpx, p[off++] + cpy,
                          p[off++] + cpx, p[off++] + cpy
                      );
                      cpx += p[off - 2];
                      cpy += p[off - 1];
                      break;
                  case 'S':
                      ctlPtx = cpx;
                      ctlPty = cpy;
                      var len = path.len();
                      var pathData = path.data;
                      if (prevCmd === CMD.C) {
                          ctlPtx += cpx - pathData[len - 4];
                          ctlPty += cpy - pathData[len - 3];
                      }
                      cmd = CMD.C;
                      x1 = p[off++];
                      y1 = p[off++];
                      cpx = p[off++];
                      cpy = p[off++];
                      path.addData(cmd, ctlPtx, ctlPty, x1, y1, cpx, cpy);
                      break;
                  case 's':
                      ctlPtx = cpx;
                      ctlPty = cpy;
                      var len = path.len();
                      var pathData = path.data;
                      if (prevCmd === CMD.C) {
                          ctlPtx += cpx - pathData[len - 4];
                          ctlPty += cpy - pathData[len - 3];
                      }
                      cmd = CMD.C;
                      x1 = cpx + p[off++];
                      y1 = cpy + p[off++];
                      cpx += p[off++];
                      cpy += p[off++];
                      path.addData(cmd, ctlPtx, ctlPty, x1, y1, cpx, cpy);
                      break;
                  case 'Q':
                      x1 = p[off++];
                      y1 = p[off++];
                      cpx = p[off++];
                      cpy = p[off++];
                      cmd = CMD.Q;
                      path.addData(cmd, x1, y1, cpx, cpy);
                      break;
                  case 'q':
                      x1 = p[off++] + cpx;
                      y1 = p[off++] + cpy;
                      cpx += p[off++];
                      cpy += p[off++];
                      cmd = CMD.Q;
                      path.addData(cmd, x1, y1, cpx, cpy);
                      break;
                  case 'T':
                      ctlPtx = cpx;
                      ctlPty = cpy;
                      var len = path.len();
                      var pathData = path.data;
                      if (prevCmd === CMD.Q) {
                          ctlPtx += cpx - pathData[len - 4];
                          ctlPty += cpy - pathData[len - 3];
                      }
                      cpx = p[off++];
                      cpy = p[off++];
                      cmd = CMD.Q;
                      path.addData(cmd, ctlPtx, ctlPty, cpx, cpy);
                      break;
                  case 't':
                      ctlPtx = cpx;
                      ctlPty = cpy;
                      var len = path.len();
                      var pathData = path.data;
                      if (prevCmd === CMD.Q) {
                          ctlPtx += cpx - pathData[len - 4];
                          ctlPty += cpy - pathData[len - 3];
                      }
                      cpx += p[off++];
                      cpy += p[off++];
                      cmd = CMD.Q;
                      path.addData(cmd, ctlPtx, ctlPty, cpx, cpy);
                      break;
                  case 'A':
                      rx = p[off++];
                      ry = p[off++];
                      psi = p[off++];
                      fa = p[off++];
                      fs = p[off++];

                      x1 = cpx, y1 = cpy;
                      cpx = p[off++];
                      cpy = p[off++];
                      cmd = CMD.A;
                      processArc(
                          x1, y1, cpx, cpy, fa, fs, rx, ry, psi, cmd, path
                      );
                      break;
                  case 'a':
                      rx = p[off++];
                      ry = p[off++];
                      psi = p[off++];
                      fa = p[off++];
                      fs = p[off++];

                      x1 = cpx, y1 = cpy;
                      cpx += p[off++];
                      cpy += p[off++];
                      cmd = CMD.A;
                      processArc(
                          x1, y1, cpx, cpy, fa, fs, rx, ry, psi, cmd, path
                      );
                      break;
              }
          }

          if (cmdStr === 'z' || cmdStr === 'Z') {
              cmd = CMD.Z;
              path.addData(cmd);
              // z may be in the middle of the path.
              cpx = subpathX;
              cpy = subpathY;
          }

          prevCmd = cmd;
      }

      path.toStatic();

      return path;
  }

  // TODO Optimize double memory cost problem
  function createPathOptions(str, opts) {
      var pathProxy = createPathProxyFromString(str);
      opts = opts || {};
      opts.buildPath = function (path) {
          if (path.setData) {
              path.setData(pathProxy.data);
              // Svg and vml renderer don't have context
              var ctx = path.getContext();
              if (ctx) {
                  path.rebuildPath(ctx);
              }
          }
          else {
              var ctx = path;
              pathProxy.rebuildPath(ctx);
          }
      };

      opts.applyTransform = function (m) {
          transformPath(pathProxy, m);
          this.dirty(true);
      };

      return opts;
  }

  /**
   * Create a Path object from path string data
   * http://www.w3.org/TR/SVG/paths.html#PathData
   * @param  {Object} opts Other options
   */
  function createFromString(str, opts) {
      return new Path(createPathOptions(str, opts));
  }

  var Path$1 = /*#__PURE__*/function () {
    function Path(config) {
      _classCallCheck(this, Path);

      //topo的视图view对象
      this.config = config || {};
      this.path = config.path;
      this.opts = config || {};
      this.rect = config.shape;
      this.view = null;
      this.init();
      return this.view;
    }
    /**
     * 初始化方法,初始化dom,初始化事件
     */


    _createClass(Path, [{
      key: "init",
      value: function init() {
        this.initView();
      }
    }, {
      key: "initView",
      value: function initView() {
        this.view = createFromString(this.path, this.opts);
        this.resizePath(this.view, this.rect);
      }
    }, {
      key: "resizePath",
      value: function resizePath(path, rect) {
        if (!path.applyTransform) {
          return;
        }

        var pathRect = path.getBoundingRect();
        rect = this.centerGraphic(rect, pathRect);
        var m = pathRect.calculateTransform(rect);
        path.applyTransform(m);
      }
    }, {
      key: "centerGraphic",
      value: function centerGraphic(rect, boundingRect) {
        // Set rect to center, keep width / height ratio.
        var aspect = boundingRect.width / boundingRect.height;
        var width = rect.height * aspect;
        var height;

        if (width <= rect.width) {
          height = rect.height;
        } else {
          width = rect.width;
          height = width / aspect;
        }

        var cx = rect.x + rect.width / 2;
        var cy = rect.y + rect.height / 2;
        return {
          x: cx - width / 2,
          y: cy - height / 2,
          width: width,
          height: height
        };
      }
    }]);

    return Path;
  }();

  /**
   * Sub-pixel optimize for canvas rendering, prevent from blur
   * when rendering a thin vertical/horizontal line.
   */

  var round = Math.round;

  /**
   * Sub pixel optimize line for canvas
   *
   * @param {Object} outputShape The modification will be performed on `outputShape`.
   *                 `outputShape` and `inputShape` can be the same object.
   *                 `outputShape` object can be used repeatly, because all of
   *                 the `x1`, `x2`, `y1`, `y2` will be assigned in this method.
   * @param {Object} [inputShape]
   * @param {number} [inputShape.x1]
   * @param {number} [inputShape.y1]
   * @param {number} [inputShape.x2]
   * @param {number} [inputShape.y2]
   * @param {Object} [style]
   * @param {number} [style.lineWidth]
   */
  function subPixelOptimizeLine(outputShape, inputShape, style) {
      var lineWidth = style && style.lineWidth;

      if (!inputShape || !lineWidth) {
          return;
      }

      var x1 = inputShape.x1;
      var x2 = inputShape.x2;
      var y1 = inputShape.y1;
      var y2 = inputShape.y2;

      if (round(x1 * 2) === round(x2 * 2)) {
          outputShape.x1 = outputShape.x2 = subPixelOptimize(x1, lineWidth, true);
      }
      else {
          outputShape.x1 = x1;
          outputShape.x2 = x2;
      }
      if (round(y1 * 2) === round(y2 * 2)) {
          outputShape.y1 = outputShape.y2 = subPixelOptimize(y1, lineWidth, true);
      }
      else {
          outputShape.y1 = y1;
          outputShape.y2 = y2;
      }
  }

  /**
   * Sub pixel optimize rect for canvas
   *
   * @param {Object} outputShape The modification will be performed on `outputShape`.
   *                 `outputShape` and `inputShape` can be the same object.
   *                 `outputShape` object can be used repeatly, because all of
   *                 the `x`, `y`, `width`, `height` will be assigned in this method.
   * @param {Object} [inputShape]
   * @param {number} [inputShape.x]
   * @param {number} [inputShape.y]
   * @param {number} [inputShape.width]
   * @param {number} [inputShape.height]
   * @param {Object} [style]
   * @param {number} [style.lineWidth]
   */
  function subPixelOptimizeRect(outputShape, inputShape, style) {
      var lineWidth = style && style.lineWidth;

      if (!inputShape || !lineWidth) {
          return;
      }

      var originX = inputShape.x;
      var originY = inputShape.y;
      var originWidth = inputShape.width;
      var originHeight = inputShape.height;

      outputShape.x = subPixelOptimize(originX, lineWidth, true);
      outputShape.y = subPixelOptimize(originY, lineWidth, true);
      outputShape.width = Math.max(
          subPixelOptimize(originX + originWidth, lineWidth, false) - outputShape.x,
          originWidth === 0 ? 0 : 1
      );
      outputShape.height = Math.max(
          subPixelOptimize(originY + originHeight, lineWidth, false) - outputShape.y,
          originHeight === 0 ? 0 : 1
      );
  }

  /**
   * Sub pixel optimize for canvas
   *
   * @param {number} position Coordinate, such as x, y
   * @param {number} lineWidth Should be nonnegative integer.
   * @param {boolean=} positiveOrNegative Default false (negative).
   * @return {number} Optimized position.
   */
  function subPixelOptimize(position, lineWidth, positiveOrNegative) {
      // Assure that (position + lineWidth / 2) is near integer edge,
      // otherwise line will be fuzzy in canvas.
      var doubledPosition = round(position * 2);
      return (doubledPosition + round(lineWidth)) % 2 === 0
          ? doubledPosition / 2
          : (doubledPosition + (positiveOrNegative ? 1 : -1)) / 2;
  }

  /**
   * 矩形
   * @module zrender/graphic/shape/Rect
   */

  // Avoid create repeatly.
  var subPixelOptimizeOutputShape = {};

  var ZrRect = Path.extend({

      type: 'rect',

      shape: {
          // 左上、右上、右下、左下角的半径依次为r1、r2、r3、r4
          // r缩写为1         相当于 [1, 1, 1, 1]
          // r缩写为[1]       相当于 [1, 1, 1, 1]
          // r缩写为[1, 2]    相当于 [1, 2, 1, 2]
          // r缩写为[1, 2, 3] 相当于 [1, 2, 3, 2]
          r: 0,

          x: 0,
          y: 0,
          width: 0,
          height: 0
      },

      buildPath: function (ctx, shape) {
          var x;
          var y;
          var width;
          var height;

          if (this.subPixelOptimize) {
              subPixelOptimizeRect(subPixelOptimizeOutputShape, shape, this.style);
              x = subPixelOptimizeOutputShape.x;
              y = subPixelOptimizeOutputShape.y;
              width = subPixelOptimizeOutputShape.width;
              height = subPixelOptimizeOutputShape.height;
              subPixelOptimizeOutputShape.r = shape.r;
              shape = subPixelOptimizeOutputShape;
          }
          else {
              x = shape.x;
              y = shape.y;
              width = shape.width;
              height = shape.height;
          }

          if (!shape.r) {
              ctx.rect(x, y, width, height);
          }
          else {
              buildPath(ctx, shape);
          }
          ctx.closePath();
          return;
      }
  });

  var ToolBox = /*#__PURE__*/function () {
    function ToolBox(toolBoxConfig, logicTopoView) {
      _classCallCheck(this, ToolBox);

      //topo对象
      this._view = logicTopoView; //zr的group

      this.id = logicTopoView._dom.id + "_utopo_toolbox_" + guid(); //contextMenu配置

      this.config = toolBoxConfig; //组

      this.viewGroup = null; //标题视图对象可以增加事件

      this.view = null; //背景视图对象

      this.backView = null; //定义好的icon

      this.icon = new Map(); //contextMenu初始化

      this.init();
    }

    _createClass(ToolBox, [{
      key: "init",
      value: function init() {
        this.initIcon();
        this.viewGroup = new Group();
        this.createDefaultOption();
        this.createToolBoxView();
        this.addDragEvent();

        this._view.add(this.viewGroup);
      }
    }, {
      key: "initIcon",
      value: function initIcon() {
        this.icon.set("fixedfocus", {
          label: '锁定焦点事件',
          icon: 'M38.8,7.7 M52.7,12h13.2v9 M65.9,26.6V32 M52.7,46.3h13.2v-9 M24.9,12H11.8v9 M11.8,26.6V32 M24.9,46.3H11.8v-9 M48.2,5.1l-9.3-9l-9.4,9.2 M38.9-3.9V12 M48.2,53.3l-9.3,9l-9.4-9.2 M38.9,62.3V46.4',
          onclick: function onclick(_view) {
            if (_view.focusFlag) {
              _view.focusFlag = false;
            } else {
              _view.focusFlag = true;
            }
          }
        });
        this.icon.set("fixeddrag", {
          label: '锁定全局拖拽事件',
          icon: 'M4,10.5V1h10.3 M20.7,1h6.1 M33,1h6.1 M55.4,10.5V1H45.2 M4,17.3v6.6 M55.6,17.3v6.6 M4,30.5V40h10.3 M20.7,40 h6.1 M33,40h6.1 M55.4,30.5V40H45.2 M21,18.9h62.9v48.6H21V18.9z',
          onclick: function onclick(_view) {
            if (_view.draggableFlag) {
              _view.draggableFlag = false;
            } else {
              _view.draggableFlag = true;
            }
          }
        });
        this.icon.set("fixedroam", {
          label: '锁定缩放事件',
          icon: 'M7.3,34.7 M0.4,10V-0.2h9.8 M89.6,10V-0.2h-9.8 M0.4,60v10.2h9.8 M89.6,60v10.2h-9.8 M12.3,22.4V10.5h13.1 M33.6,10.5h7.8 M49.1,10.5h7.8 M77.5,22.4V10.5h-13 M12.3,31.1v8.2 M77.7,31.1v8.2 M12.3,47.6v11.9h13.1 M33.6,59.5h7.6 M49.1,59.5 h7.7 M77.5,47.6v11.9h-13',
          onclick: function onclick(_view) {
            if (_view.roamFlag) {
              _view.roamFlag = false;
            } else {
              _view.roamFlag = true;
            }
          }
        });
        this.icon.set("zoom_in", {
          label: '放大',
          icon: 'M39,39.5 C34.9,43.5 29.2,46 23,46 C10.3,46 0,35.7 0,23 C0,10.3 10.3,0 23,0 C35.7,0 46,10.3 46,23 C46,27.4 44.8,31.5 42.6,35 L42.7,35.1 L61.6,54 C62.4,54.8 61.5,56.9 59.7,58.7 L59.7,58.7 C57.9,60.5 55.7,61.4 55,60.6 L36,41.6 M15.1,23 L31,23 M23,16 L23,31',
          onclick: function onclick(_view) {
            _view.scaleAdd(0.1);
          }
        });
        this.icon.set("zoom_restore", {
          label: '还原',
          icon: 'm512 354c0 5.046875-1.957031 10.210938-5.8125 14.09375-.015625.015625-.027344.03125-.042969.046875l-118 118c-7.8125 7.8125-20.476562 7.8125-28.285156 0-7.8125-7.808594-7.8125-20.472656 0-28.28125l83.855469-83.859375h-423.714844c-11.046875 0-20-8.953125-20-20s8.953125-20 20-20h423.714844l-83.855469-83.859375c-7.8125-7.808594-7.8125-20.472656 0-28.28125 7.808594-7.8125 20.472656-7.8125 28.285156 0l118 118c.015625.015625.027344.03125.042969.050781 3.746094 3.769532 5.8125 8.886719 5.8125 14.089844zm-512-216c0 5.046875 1.957031 10.210938 5.8125 14.09375.015625.015625.027344.03125.042969.046875l118 118c7.8125 7.8125 20.476562 7.8125 28.285156 0 7.8125-7.808594 7.8125-20.472656 0-28.28125l-83.855469-83.859375h423.714844c11.046875 0 20-8.953125 20-20s-8.953125-20-20-20h-423.714844l83.855469-83.859375c7.8125-7.808594 7.8125-20.472656 0-28.28125-7.808594-7.8125-20.472656-7.8125-28.285156 0l-118 118c-.015625.015625-.027344.03125-.042969.050781-3.746094 3.769532-5.8125 8.886719-5.8125 14.089844zm0 0',
          onclick: function onclick(_view) {
            _view.scaleTo(1);
          }
        });
        this.icon.set("zoom_out", {
          label: '缩小',
          icon: 'M39.1,39.5 C35,43.5 29.3,46 23.1,46 C10.4,46 0.1,35.7 0.1,23 C0.1,10.3 10.4,0 23.1,0 C35.8,0 46.1,10.3 46.1,23 C46.1,27.4 44.9,31.5 42.7,35 L42.8,35.1 L61.7,54 C62.5,54.8 61.6,56.9 59.8,58.7 L59.8,58.7 C58,60.5 55.8,61.4 55.1,60.6 L36.1,41.6 M15.2,23 L31.1,23',
          onclick: function onclick(_view) {
            _view.scaleAdd(-0.1);
          }
        });
        this.icon.set("saveasimage", {
          label: '保存为图片',
          icon: 'M4.7,22.9L29.3,45.5L54.7,23.4M4.6,43.6L4.6,58L53.8,58L53.8,43.6M29.2,45.1L29.2,0',
          onclick: function onclick(_view) {
            _view._toolbox.viewGroup.ignore = true;

            _view._topo.toImage();

            _view._toolbox.viewGroup.ignore = false;
          }
        });
        this.icon.set("restore", {
          label: '还原',
          icon: 'M3.8,33.4 M47,18.9h9.8V8.7 M56.3,20.1 C52.1,9,40.5,0.6,26.8,2.1C12.6,3.7,1.6,16.2,2.1,30.6 M13,41.1H3.1v10.2 M3.7,39.9c4.2,11.1,15.8,19.5,29.5,18 c14.2-1.6,25.2-14.1,24.7-28.5'
        });
        this.icon.set("back", {
          label: '返回',
          icon: 'M22,1.4L9.9,13.5l12.3,12.3 M10.3,13.5H54.9v44.6 H10.3v-26'
        });
        this.icon.set("line", {
          label: '折线',
          icon: 'M4.1,28.9h7.1l9.3-22l7.4,38l9.7-19.7l3,12.8h14.9M4.1,58h51.4'
        });
        this.icon.set("line", {
          label: '折线',
          icon: 'M4.1,28.9h7.1l9.3-22l7.4,38l9.7-19.7l3,12.8h14.9M4.1,58h51.4'
        });
        this.icon.set("line", {
          label: '折线',
          icon: 'M4.1,28.9h7.1l9.3-22l7.4,38l9.7-19.7l3,12.8h14.9M4.1,58h51.4'
        });
        this.icon.set("bar", {
          label: '柱状',
          icon: 'M6.7,22.9h10V48h-10V22.9zM24.9,13h10v35h-10V13zM43.2,2h10v46h-10V2zM3.1,58h53.7'
        });
        this.icon.set("bars", {
          label: '堆叠',
          icon: 'M8.2,38.4l-8.4,4.1l30.6,15.3L60,42.5l-8.1-4.1l-21.5,11L8.2,38.4z M51.9,30l-8.1,4.2l-13.4,6.9l-13.9-6.9L8.2,30l-8.4,4.2l8.4,4.2l22.2,11l21.5-11l8.1-4.2L51.9,30z M51.9,21.7l-8.1,4.2L35.7,30l-5.3,2.8L24.9,30l-8.4-4.1l-8.3-4.2l-8.4,4.2L8.2,30l8.3,4.2l13.9,6.9l13.4-6.9l8.1-4.2l8.1-4.1L51.9,21.7zM30.4,2.2L-0.2,17.5l8.4,4.1l8.3,4.2l8.4,4.2l5.5,2.7l5.3-2.7l8.1-4.2l8.1-4.2l8.1-4.1L30.4,2.2z'
        });
        this.icon.set("cust", {
          label: '自定义',
          icon: 'M55.2,34.9c1.7,0,3.1,1.4,3.1,3.1s-1.4,3.1-3.1,3.1 s-3.1-1.4-3.1-3.1S53.5,34.9,55.2,34.9z M50.4,51c1.7,0,3.1,1.4,3.1,3.1c0,1.7-1.4,3.1-3.1,3.1c-1.7,0-3.1-1.4-3.1-3.1 C47.3,52.4,48.7,51,50.4,51z M55.6,37.1l1.5-7.8 M60.1,13.5l1.6-8.7l-7.8,4 M59,19l-1,5.3 M24,16.1l6.4,4.9l6.4-3.3 M48.5,11.6 l-5.9,3.1 M19.1,12.8L9.7,5.1l1.1,7.7 M13.4,29.8l1,7.3l6.6,1.6 M11.6,18.4l1,6.1 M32.8,41.9 M26.6,40.4 M27.3,40.2l6.1,1.6 M49.9,52.1l-5.6-7.6l-4.9-1.2'
        });
        this.icon.set("cust1", {
          label: '自定义1',
          icon: 'm-134.2,333.4c0,0 -22.7,-71.1 55.7,-51.1c0,0 50.4,-50.4 66.3,12.9c0,0 59.5,-2.3 32.6,63.6c0,0 68.2,27.3 2.7,72c0,0 62.5,51.9 -5.3,78c0,0 18.6,67.8 -50.8,53c0,0 -47.7,47.7 -70.1,-11.7c0,0 -61.4,24.2 -42,-56.4c0,0 -71.2,-25.4 -3.8,-81.4c-0.1,-0.1 -59.6,-63 14.7,-78.9z'
        });
      }
      /**
       * 默认配置值对象数据
       */

    }, {
      key: "createDefaultOption",
      value: function createDefaultOption() {
        var defaultOption = {
          //默认图形层级在100
          zlevel: 100,
          left: 0,
          bottom: 0,
          padding: 5,
          draggable: false,
          title: {
            h: 30,
            textFill: '#000',
            color: '#30B3CA'
          },
          back: {
            color: '#000'
          },
          item: {
            imageWidth: 16,
            rectWidth: 30,
            rectHeight: 10,
            textFill: '#000'
          }
        }; //合并属性

        merge(defaultOption, this.config, true);
        this.config = defaultOption;
      }
      /**
       * 清空的方法
       */

    }, {
      key: "clear",
      value: function clear() {
        this.viewGroup.removeAll();

        this._view.remove(this.viewGroup);
      }
      /**
       * 创建legend视图
       */

    }, {
      key: "createToolBoxView",
      value: function createToolBoxView() {
        this.createToolboxRect(this);
        this.createToolBoxItem(this);
        this.correctPosition();
      }
      /**
       * 校验位置,根据配置
       */

    }, {
      key: "correctPosition",
      value: function correctPosition() {
        var rect = this.viewGroup.getBoundingRect();
        console.log(rect); //修改title大小
        //修改组的位置,保证所有的图例都能看到

        var zWidth = this._view._zrender.getWidth(); //修改背景大小


        this.backView.attr('shape', {
          width: rect.width + 7,
          height: rect.height
        });
        var position = [zWidth - rect.width - 20, 20];
        this.viewGroup.attr('position', position);
      }
      /**
       * 增加工具栏拖拽事件
       */

    }, {
      key: "addDragEvent",
      value: function addDragEvent() {
        var _this = this;

        this.backView.drift = function (dx, dy) {
          _this.viewGroup.drift(dx, dy);
        };
      }
      /**
       * 创建工具栏背景rect
       * @param {} model 
       */

    }, {
      key: "createToolboxRect",
      value: function createToolboxRect(model) {
        this.backView = new ZrRect({
          zlevel: 99,
          position: [0, 0],
          shape: {
            x: 0,
            y: 0,
            r: [5, 5, 5, 5]
          },
          style: {
            lineWidth: 1,
            fill: null,
            stroke: '#D6E5F7',
            lineDash: [1, 5],
            opacity: 1
          },
          hoverable: false,
          draggable: true,
          clickable: false
        });
        this.viewGroup.add(this.backView);
      }
    }, {
      key: "createToolBoxItem",
      value: function createToolBoxItem() {
        var _this2 = this;

        //let toolBox = ['fixedroam','fixeddrag','fixedfocus','saveasimage','back','line','bar','bars','cust']
        var toolBox = ['fixedroam', 'fixeddrag', 'fixedfocus', 'saveasimage', 'zoom_in', 'zoom_restore', 'zoom_out']; //全局x值

        var x = 10; //循环生成toolbox中item

        toolBox.forEach(function (itemid) {
          var icon = _this2.icon.get(itemid);

          if (icon != null) {
            var itemTemp = clone(icon);
            itemTemp.x = x;

            var itemView = _this2.createLegendPathItemData(itemTemp);

            _this2.viewGroup.add(itemView);

            x += 25;
          }
        });
      }
      /**
       * 创建item对象
       * @param {} item 
       */

    }, {
      key: "createLegendPathItemData",
      value: function createLegendPathItemData(item) {
        var _this3 = this;

        //图例图片大小20*20
        var itemSize = 15;
        var config = {
          zlevel: 100,
          style: {
            fill: null,
            stroke: '#6B6B6B',
            textPosition: 'bottom',
            textFill: null
          },
          shape: {
            x: item.x,
            y: 5,
            width: itemSize,
            height: itemSize
          },
          strokeNoScale: true,
          hoverable: false,
          draggable: false,
          clickable: false,
          onclick: function onclick() {
            item.onclick(_this3._view);
          }
        };
        config.path = item.icon;
        var imgItem = new Path$1(config); //绑定鼠标滑过事件

        imgItem.on('mouseover', function () {
          imgItem.attr("style", {
            text: item.label,
            textFill: '#63ABCF',
            stroke: '#63ABCF'
          });
        }).on('mouseout', function () {
          imgItem.attr("style", {
            textFill: null,
            stroke: '#6B6B6B'
          });
        });
        return imgItem;
      }
    }]);

    return ToolBox;
  }();

  /**
   * @param {Array.<Object>} colorStops
   */
  var Gradient = function (colorStops) {

      this.colorStops = colorStops || [];

  };

  Gradient.prototype = {

      constructor: Gradient,

      addColorStop: function (offset, color) {
          this.colorStops.push({

              offset: offset,

              color: color
          });
      }

  };

  /**
   * x, y, x2, y2 are all percent from 0 to 1
   * @param {number} [x=0]
   * @param {number} [y=0]
   * @param {number} [x2=1]
   * @param {number} [y2=0]
   * @param {Array.<Object>} colorStops
   * @param {boolean} [globalCoord=false]
   */
  var LinearGradient = function (x, y, x2, y2, colorStops, globalCoord) {
      // Should do nothing more in this constructor. Because gradient can be
      // declard by `color: {type: 'linear', colorStops: ...}`, where
      // this constructor will not be called.

      this.x = x == null ? 0 : x;

      this.y = y == null ? 0 : y;

      this.x2 = x2 == null ? 1 : x2;

      this.y2 = y2 == null ? 0 : y2;

      // Can be cloned
      this.type = 'linear';

      // If use global coord
      this.global = globalCoord || false;

      Gradient.call(this, colorStops);
  };

  LinearGradient.prototype = {

      constructor: LinearGradient
  };

  inherits(LinearGradient, Gradient);

  /**
   * utopoViewGrid的绘图层，调用zrender 绘制图
   * @constructor
   */

  var Grid = /*#__PURE__*/function () {
    function Grid(config, UTopoView) {
      _classCallCheck(this, Grid);

      this._view = UTopoView;
      this._config = config;
      this._zrender = this._view._zrender;
      this.zrdom = this._view._dom;
      this.backview = null;
      this.init();
    }

    _createClass(Grid, [{
      key: "init",
      value: function init() {
        this.bgid = this.zrdom.id + "_utopo_background";
        this.addBackGroundView();
      }
      /**
       * 添加zrender背景
       */

    }, {
      key: "addBackGroundView",
      value: function addBackGroundView() {
        var zrender = this._zrender;

        if (!this._config.background) {
          return;
        } //获取background配置


        var backgroundConfig = this._config.background; //zrender大小

        var width = this._zrender.getWidth();

        var height = this._zrender.getHeight(); //配置了背景图片


        if (backgroundConfig.image) {
          var img = new Image(); //新建一个图片对象//

          img.src = backgroundConfig.image; //将图片的src属性赋值给新建的图片对象的src
          //图片加载完之后进行的操作

          img.onload = function () {
            var imgWidth = img.width;
            var imgHeight = img.height;
            console.log(imgWidth, imgHeight);
            var x = 0,
                y = 0,
                w = 0,
                h = 0; //按照宽画图

            if (imgWidth / imgHeight > width / height) {
              x = 0;
              y = (height - width * imgHeight / imgWidth) / 2;
              w = width;
              h = width * imgHeight / imgWidth;
            } else {
              //按照高画
              x = (width - height * imgWidth / imgHeight) / 2;
              y = 0;
              w = height * imgWidth / imgHeight;
              h = height;
            }

            var bgimgView = new ZImage({
              type: 'background',
              zlevel: 0,
              style: {
                x: x,
                y: y,
                width: w,
                height: h,
                image: backgroundConfig.image
              }
            });
            zrender.add(bgimgView);
          };
        } else if (backgroundConfig.color) {
          //默认为null的颜色,无颜色
          var backgroundColor = null; //是字符配置

          if (isString(backgroundConfig.color)) {
            backgroundColor = backgroundConfig.color;
          } else if (isArray(backgroundConfig.color)) {
            //配置渐变色
            //渐变色arr
            var colorArr = backgroundConfig.color;

            if (colorArr.length === 1) {
              colorArr.push(colorArr[0]);
            }

            var direct = backgroundConfig.direct ? backgroundConfig.direct : 'top'; //从上到下的渐变

            if ("top" === direct) {
              backgroundColor = new LinearGradient(0, 0, 0, height, null, true);
            } //从下到上的渐变


            if ("bottom" === direct) {
              backgroundColor = new LinearGradient(0, height, 0, 0, null, true);
            } //从左到右的渐变


            if ("left" === direct) {
              backgroundColor = new LinearGradient(0, 0, width, 0, null, true);
            } //从右到左的渐变


            if ("right" === direct) {
              backgroundColor = new LinearGradient(width, 0, 0, 0, null, true);
            }

            var d = 1 / (colorArr.length - 1);

            for (var i = 0; i < colorArr.length; i++) {
              backgroundColor.addColorStop(d * i, colorArr[i]);
            }
          } //生成背景色的图形对象


          this.backview = new ZrRect({
            type: 'background',
            id: this.bgid,
            z: 0,
            zlevel: 1,
            shape: {
              x: 0,
              y: 0,
              width: width,
              height: height
            },
            style: {
              fill: backgroundColor
            },
            hoverable: false,
            draggable: false,
            clickable: false
          });
          zrender.add(this.backview);
        }
      }
    }, {
      key: "resize",
      value: function resize() {
        var zrender = this._zrender;
        var width = zrender.getWidth();
        var height = zrender.getHeight();

        if (this.backview) {
          this.backview.attr('shape', {
            width: width,
            height: height
          });
        }
      }
    }]);

    return Grid;
  }();

  /**
   * UTopo 子对象mole类父类,点线组继承其实现
   * @param model
   * @author zhangnt
   */

  var UTopoModel = /*#__PURE__*/function () {
    function UTopoModel(model) {
      _classCallCheck(this, UTopoModel);

      this._storage = null; //唯一id

      this.id = model.id; //标记点、线、组的类型,同时可以用于每一个点线组内的标记图形等类型 例如：点的symbol对象类型 point_symbol

      this.mtype = model.mtype; //中文名称

      this.name = model.name; //对象父亲id

      this.pid = model.parentid ? model.parentid : model.pid ? model.pid : null; //绑定配置对象

      this.topoinfo = model; //对象层级,在某些特定时候可以配置z

      this.z = 1; //是否不响应鼠标事件,默认为false

      this.silent = model.silent; //是否可以拖拽,目前只有点有这个属性

      this.draggable = model.hasOwnProperty('draggable') && model.draggable === false ? false : true; //图形配置对象

      this.style = model.viewConf ? model.viewConf : model.style ? model.style : {};
      this.hstyle = model.hstyle ? model.hstyle : {}, //动画配置对象
      this.animation = model.animation || {}; //点,线对象的闪烁涟漪效果配置对象

      this.flicker = model.flicker || {}; //线上的标记文字配置,以后点上也可能扩展

      this.sign = model.sign || null; //对象上的标记属性,表示图形状态

      this.symbol = model.symbol || [];
      this.view = null;
      this.viewgroup = null;
      this.highlightFlag = false; //绑定点击事件

      this.onclick = model.onclick;
      this.ondblclick = model.ondblclick; //父类事件,修改图片透明度,停止开始动画效果
    } //焦点事件


    _createClass(UTopoModel, [{
      key: "focus",
      value: function focus(flag) {
        if (!this._storage._view.focusFlag) {
          return;
        }

        if (flag) {
          this.viewgroup.children().forEach(function (el) {
            el.attr("style", {
              opacity: 1
            });

            if (el.animators.length !== 0) {
              el.animators.forEach(function (animate) {
                //去除非路径动画的内容
                if (!animate.path && animate.isPaused()) {
                  animate.resume();
                }
              });
            }

            if (el.amtype) {
              el.attr("invisible", false);
            }
          });
        } else {
          this.viewgroup.children().forEach(function (el) {
            el.attr("style", {
              opacity: 0.3
            });

            if (el.animators.length !== 0) {
              el.animators.forEach(function (animate) {
                if (!animate.path) {
                  animate.pause();
                  flag = true;
                }
              });
            }

            if (el.amtype) {
              el.attr("invisible", true);
            }
          });
        }
      } //高亮状态

    }, {
      key: "highlight",
      value: function highlight(flag) {} //model类中自行实现
      //绑定样式

    }, {
      key: "attrstyle",
      value: function attrstyle(style) {
        if (this.view != null) {
          this.view.attr("style", style);
        } else {
          console.log("model attr error" + this);
        }
      }
    }]);

    return UTopoModel;
  }();

  var Cloud = /*#__PURE__*/function () {
    function Cloud(config) {
      _classCallCheck(this, Cloud);

      //topo的视图view对象
      this.config = config || {};
      this.path = 'm-134.2,333.4c0,0 -22.7,-71.1 55.7,-51.1c0,0 50.4,-50.4 66.3,12.9c0,0 59.5,-2.3 32.6,63.6c0,0 68.2,27.3 2.7,72c0,0 62.5,51.9 -5.3,78c0,0 18.6,67.8 -50.8,53c0,0 -47.7,47.7 -70.1,-11.7c0,0 -61.4,24.2 -42,-56.4c0,0 -71.2,-25.4 -3.8,-81.4c-0.1,-0.1 -59.6,-63 14.7,-78.9z';
      this.opts = config || {};
      this.rect = config.shape;
      this.view = null;
      this.init();
      this.view.cloud = this;
      return this.view;
    }
    /**
     * 初始化方法,初始化dom,初始化事件
     */


    _createClass(Cloud, [{
      key: "init",
      value: function init() {
        this.initView();
      }
    }, {
      key: "initView",
      value: function initView() {
        this.opts.type = 'cloud';
        this.view = createFromString(this.path, this.opts);
        this.resizePath(this.rect);
      }
    }, {
      key: "resizePath",
      value: function resizePath(rect) {
        var pathRect = this.view.getBoundingRect();
        var m = pathRect.calculateTransform(rect);
        this.view.applyTransform(m);
      }
    }]);

    return Cloud;
  }();

  var GroupConfig = {
    "default": {
      fontSize: 12,
      fontFamily: 'Microsoft YaHei',
      textFill: '#000',
      textPosition: 'inside'
    },
    rect: {
      stroke: '#000'
    }
  };

  var PointConfig = {
    "default": {
      r: 15,
      width: 30,
      height: 30,
      color: 'green',
      textFill: '#000',
      fontSize: 12,
      fontFamily: 'Microsoft YaHei',
      textPosition: 'bottom'
    },
    //鼠标滑过高亮
    emphasis: {
      //高亮颜色
      stroke: 'yellow',
      //高亮加半径
      r: +5,
      //高亮圈宽度
      lineWidth: 3,
      //高亮圈填充
      fill: null
    },
    //点的闪烁涟漪效果
    flicker: {
      color: 'red'
    },
    //点选中高亮颜色
    select: {
      color: '#D15F55'
    },
    sign: {
      //标记配置
      text: null,
      //文字基于点中心的位置，文字的左上角位置
      p: [0, 0],
      //文字默认大小12
      fontSize: 12,
      //字体颜色
      color: '#000',
      //背景颜色
      backgroundColor: null,
      //内边距
      padding: 0,
      //文字行高
      textLineHeight: 16
    }
  };

  var GroupProxy = /*#__PURE__*/function () {
    function GroupProxy() {
      _classCallCheck(this, GroupProxy);
    }

    _createClass(GroupProxy, [{
      key: "createView",
      value: function createView(model) {
        if (model.mtype === 'img') {
          return UGroupUtil.createGroupImage(model);
        }

        if (model.mtype === 'rect') {
          return UGroupUtil.createGroupRect(model);
        }

        if (model.mtype === 'cloud') {
          return UGroupUtil.createGroupCloud(model);
        }
      }
    }, {
      key: "updateEl",
      value: function updateEl(el, model) {
        if (el instanceof ZImage && el.gtype !== 'symbol') {
          var rect = getRect(model);
          var style = {
            x: rect.x,
            y: rect.y,
            width: rect.width,
            height: rect.height
          };
          el.attr('style', style);
        }

        if (el instanceof ZImage && el.gtype === 'symbol') {
          var _rect2 = getRect(model);

          var _style = this.getSymbolPosition(_rect2, el.gdata);

          el.attr('style', _style);
        }

        if (el instanceof ZrRect) {
          var _rect3 = getRect(model);

          var shape = {
            x: _rect3.x,
            y: _rect3.y,
            width: _rect3.width,
            height: _rect3.height
          };
          el.attr('shape', shape);
        }

        if (el instanceof Path && el.type === 'cloud') {
          var _rect4 = getRect(model);

          var _shape = {
            x: _rect4.x,
            y: _rect4.y,
            width: _rect4.width,
            height: _rect4.height
          };
          el.cloud.resizePath(_shape);
        }
      }
    }, {
      key: "updateElLevel",
      value: function updateElLevel(el, model) {
        el.attr('z', model.z);
      }
      /**
      * 创建点上的symbol图片对象,
      * 可配置：
      *      位置
      *      图形
      *      单击事件
      */

    }, {
      key: "createSymbolView",
      value: function createSymbolView(model) {
        var _this = this;

        //获取点的区域位置
        var rect = getRect(model); //返回数组对象

        var symbolViews = [];
        model.symbol.forEach(function (s) {
          try {
            var style = _this.getSymbolStyle(s);

            var style1 = _this.getSymbolPosition(rect, s);

            merge(style, style1, true);
            style.image = s.image; //生成图标

            var imageOption = {
              gtype: 'symbol',
              gdata: s,
              zlevel: model.zlevel + 100012,
              position: [4, -4],
              style: style,
              draggable: false
            };
            var symbolImg = new ZImage(imageOption);
            symbolImg.on("click", function () {
              s.onclick(model);
            });
            symbolViews.push(symbolImg);
          } catch (e) {
            console.warn(e);
          }
        });
        return symbolViews;
      }
      /**
       * 获取图标位置,默认右上角
       * @param {} rect 
       * @param {*} s 
       */

    }, {
      key: "getSymbolPosition",
      value: function getSymbolPosition(rect, s) {
        //计算图标位置
        var x = rect.x,
            y = rect.y;
        var width = s.width ? s.width : 15;
        var height = s.height ? s.height : 15;
        var dx = rect.width - width / 3 * 2;
        var dy = -height / 3;
        var position = s.position;

        if (position instanceof Array) {
          dx = position[0];
          dy = position[1];
        }

        x = x + dx;
        y = y + dy;
        return {
          x: x,
          y: y,
          width: width,
          height: height
        };
      }
    }, {
      key: "getSymbolStyle",
      value: function getSymbolStyle(s) {
        var style = {
          fill: PointConfig["default"].color,
          text: s.text ? s.text : null,
          fontSize: s.fontSize ? s.fontSize : PointConfig["default"].fontSize,
          fontFamily: s.fontFamily ? s.fontFamily : PointConfig["default"].fontFamily,
          textPosition: 'inside',
          textFill: s.textFill ? s.textFill : PointConfig["default"].textFill,
          transformText: true
        };
        return style;
      }
    }]);

    return GroupProxy;
  }();
  var UGroupUtil = {
    createGroupImage: function createGroupImage(model) {
      var rect = getRect(model); //style支持zrender配置

      var style = {
        x: rect.x,
        y: rect.y,
        width: rect.width,
        height: rect.height,
        image: model.style.image,
        text: model.name ? model.name : model.style.text ? model.style.text : '',
        fontSize: GroupConfig["default"].fontSize,
        fontFamily: GroupConfig["default"].fontFamily,
        textPosition: GroupConfig["default"].textPosition,
        textFill: GroupConfig["default"].textFill,
        transformText: true
      }; //合并配置

      merge(style, model.style, true);
      var zrImageOption = {
        zlevel: model.zlevel,
        style: style,
        draggable: model.draggable
      };
      return new ZImage(zrImageOption);
    },
    createGroupRect: function createGroupRect(model) {
      var rect = getRect(model); //style支持zrender配置

      var style = {
        fill: null,
        stroke: GroupConfig.rect.stroke,
        lineWidth: 1,
        text: model.name ? model.name : model.style.text ? model.style.text : '',
        fontSize: GroupConfig["default"].fontSize,
        fontFamily: GroupConfig["default"].fontFamily,
        textPosition: GroupConfig["default"].textPosition,
        textFill: GroupConfig["default"].textFill
      };
      merge(style, model.style, true);
      var zrRectOption = {
        zlevel: model.zlevel,
        shape: {
          x: rect.x,
          y: rect.y,
          width: rect.width,
          height: rect.height,
          r: model.style.r ? model.style.r : 0
        },
        style: style,
        draggable: model.draggable
      };
      return new ZrRect(zrRectOption);
    },
    createGroupCloud: function createGroupCloud(model) {
      var rect = getRect(model); //style支持zrender配置

      var style = {
        fill: '#D5EEF9',
        stroke: '#01A0E9',
        lineWidth: 1,
        lineDash: [2, 2],
        text: model.name ? model.name : model.style.text ? model.style.text : '',
        fontSize: GroupConfig["default"].fontSize,
        fontFamily: GroupConfig["default"].fontFamily,
        textPosition: GroupConfig["default"].textPosition,
        textFill: GroupConfig["default"].textFill
      };
      merge(style, model.style, true);
      var zrRectOption = {
        zlevel: model.zlevel,
        shape: {
          x: rect.x,
          y: rect.y,
          width: rect.width,
          height: rect.height
        },
        style: style,
        draggable: model.draggable
      };
      return new Cloud(zrRectOption);
    }
  };
  /**
   * 获取组下所有点组合成的rect大小,返回一个矩形大小
   */

  function getRect(model) {
    var _insideRect = null;

    var allChildList = model._storage.getPointsByParentId(model.id);

    if (allChildList.length !== 0) {
      var minX = 100000,
          minY = 100000,
          maxX = -100000,
          maxY = -100000;
      allChildList.forEach(function (point) {
        if (point) {
          var _rect = point.getInRect(); //左上角坐标


          var x = _rect.x;
          var y = _rect.y;
          var width = _rect.width;
          var height = _rect.height;
          /**增加外边框padding*/

          if (x < minX) {
            minX = x;
          }

          if (y <= minY) {
            minY = y;
          }

          if (x + width > maxX) {
            maxX = x + width;
          }

          if (y + height > maxY) {
            maxY = y + height;
          }
        }
      });
      _insideRect = {
        x: minX,
        y: minY,
        width: Math.abs(maxX - minX),
        height: Math.abs(maxY - minY)
      };
    } //如果展开云图下面没有子节点
    else {
        _insideRect = {
          x: 0,
          y: 0,
          width: 50,
          height: 50
        };
      } //计算坐标padding


    if (!model.style.padding) {
      var paddingRate = model.paddingRate;
      _insideRect.x = _insideRect.x - _insideRect.width * paddingRate;
      _insideRect.y = _insideRect.y - _insideRect.height * paddingRate;
      _insideRect.width = _insideRect.width * (1 + 2 * paddingRate);
      _insideRect.height = _insideRect.height * (1 + 2 * paddingRate);
    } else {
      var a, b, c, d;
      var padding = model.style.padding;

      if (!isNaN(padding)) {
        a = padding;
        b = padding;
        c = padding;
        d = padding;
      }

      if (padding instanceof Array) {
        if (padding.length == 1) {
          a = padding[0];
          b = padding[0];
          c = padding[0];
          d = padding[0];
        }

        if (padding.length == 2) {
          a = padding[0];
          b = padding[1];
          c = padding[0];
          d = padding[1];
        }

        if (padding.length == 3) {
          a = padding[0];
          b = padding[1];
          c = padding[2];
          d = padding[1];
        }

        if (padding.length == 4) {
          a = padding[0];
          b = padding[1];
          c = padding[2];
          d = padding[3];
        }
      }

      _insideRect.x = _insideRect.x - b;
      _insideRect.y = _insideRect.y - a;
      _insideRect.width = _insideRect.width + b + d;
      _insideRect.height = _insideRect.height + a + c;
    }

    return _insideRect;
  }

  /**
   * utopo中组的概念,封装zrender的group,生成其默认的getBoundingRect()绘制图形,用image生成
   * @author zhangnt
   * @date 2019-3-5 16:58
   * @param groupConfig
   * @constructor
   */

  var Group$1 = /*#__PURE__*/function (_UTopoModel) {
    _inherits(Group$1, _UTopoModel);

    var _super = _createSuper(Group$1);

    function Group$1(groupConfig) {
      var _this;

      _classCallCheck(this, Group$1);

      _this = _super.call(this, groupConfig);
      _this.zlevel = GlobalConfig.level.GROUP_LEVEL;
      _this.z = 1; //边距比例

      _this.paddingRate = 0.3;
      _this.groupProxy = null;

      _this.init(groupConfig);

      return _this;
    }

    _createClass(Group$1, [{
      key: "init",
      value: function init(groupConfig) {
        //组默认为图片类型
        if (!this.mtype) {
          this.mtype = GlobalConfig.mtype.GROUP_CORE_IMG;
        }

        if (this.mtype == 'rect') {
          this.paddingRate = 0.1;
        } //组的view对象的生产对象


        this.groupProxy = new GroupProxy();
      }
    }, {
      key: "initView",
      value: function initView(_storage) {
        this._storage = _storage;

        if (_storage._config.draggable == false) {
          this.draggable = false;
        }

        this.viewgroup = new Group();
        this.createPointSymbolView();
        this.createZrGroupView(); //添加拖拽事件

        this.addDragEvent();
      }
      /**
       * 计算组级别
       */

    }, {
      key: "updateViewLevel",
      value: function updateViewLevel() {
        var _this2 = this;

        this.calculateLevel(this.pid);
        this.viewgroup.children().forEach(function (el) {
          _this2.groupProxy.updateElLevel(el, _this2);
        });
      }
      /**
       * 计算层级
       * @param pid
       */

    }, {
      key: "calculateLevel",
      value: function calculateLevel(pid) {
        var g = this._storage.getPoint(pid);

        if (g != null) {
          this.z++;
          this.calculateLevel(g.pid);
        }
      }
      /**
       * 创建组的view对象
       * this.viewgroup = 组  管理所有数据
       * this.view = image  主要背景
       * @returns {ZImage}
       */

    }, {
      key: "createZrGroupView",
      value: function createZrGroupView() {
        //创建view
        this.view = this.groupProxy.createView(this); //加入组

        this.viewgroup.add(this.view);
        this.view.model = this;
        this.viewgroup.model = this;
      }
      /**
       * 更新自身view
       */

    }, {
      key: "updateView",
      value: function updateView() {
        var _this3 = this;

        if (this.viewgroup != null) {
          this.viewgroup.children().forEach(function (el) {
            _this3.groupProxy.updateEl(el, _this3);
          });
        }

        this.updateUpGroupView();
      }
      /**
       * 更新组内孩子的的view
       * @param dx
       * @param dy
       */

    }, {
      key: "updateChildView",
      value: function updateChildView(dx, dy) {
        var allChildList = this._storage.getPointsByParentId(this.id);

        allChildList.forEach(function (el) {
          el.updateView(dx, dy);
        });
      }
      /**
       * 添加拖拽事件
       */

    }, {
      key: "addDragEvent",
      value: function addDragEvent() {
        //修改对应点的drift方法,修改xy值不修改position
        //获取当前点相关联的所有线
        var that = this;

        this.view.drift = function (dx, dy) {
          var x = this.style.x;
          var y = this.style.y;
          var scale = this.scale;
          this.attr("style", {
            x: x + dx / scale[0],
            y: y + dy / scale[1]
          });
          that.updateChildView(dx, dy);
          that.updateUpGroupView();
        };
      }
      /**
       * 更新父层视图
       */

    }, {
      key: "updateUpGroupView",
      value: function updateUpGroupView() {
        if (this.pid != null) {
          var shape = this._storage.getPoint(this.pid);

          if (shape) {
            shape.updateView();
          }
        }
      }
      /**
       * 获取点对象的包围盒大小
       */

    }, {
      key: "getInRect",
      value: function getInRect() {
        return this.view.getBoundingRect();
      }
      /**
       * 创建组对象的右上角图标
       */

    }, {
      key: "createPointSymbolView",
      value: function createPointSymbolView() {
        var _this4 = this;

        if (this.symbol && this.symbol instanceof Array && this.symbol.length > 0) {
          var symbol = this.groupProxy.createSymbolView(this);
          symbol.forEach(function (el) {
            _this4.viewgroup.add(el);
          });
        }
      }
    }]);

    return Group$1;
  }(UTopoModel);

  /**
   * utopoViewGrid的绘图层的事件对象
   * @author zhangnt
   */

  var GlobalEvent = /*#__PURE__*/function () {
    function GlobalEvent(UTopoView) {
      _classCallCheck(this, GlobalEvent);

      this._view = UTopoView;
      this._config = UTopoView._config;
      this._zrender = this._view._zrender;
      this.zrdom = this._view._dom;
      this.rawTransformable = null;
      this.roamTransformable = null; //记录缩放值

      this.scale = 1;
      this.minScale = 0.3; //事件处理的zrenderShape对象

      this.evview = null;
      this.init();
    }

    _createClass(GlobalEvent, [{
      key: "init",
      value: function init() {
        //用来进行缩放的工具
        this.rawTransformable = new Group();
        this.roamTransformable = new Group();
        this.roamTransformable.add(this.rawTransformable); //事件对象id

        this.bgevid = this.zrdom.id + "_utopo_background_ev"; //添加处理事件的背景对象

        this.addBackGroundEventView(); //添加全局拖拽事件

        this.addGlobalDragEvent(); //添加全局缩放事件

        this.addGlobalScaleEvent(); //增加全局事件关联高亮显示事件

        this.addGlobalMouseFocusEvent(); //增加全局鼠标点击事件,分单击双击事件

        this.addGlobalMouseClickEvent();
        this.addGlobalMouseClickEventDefault();
      }
    }, {
      key: "getScale",
      value: function getScale() {
        return this.scale;
      }
      /**
       * 加zrender的事件背景图形,默认为一个巨大的矩形,铺满整个zrender绘图区域.
       */

    }, {
      key: "addBackGroundEventView",
      value: function addBackGroundEventView() {
        var zrender = this._zrender;

        var width = this._zrender.getWidth();

        var height = this._zrender.getHeight();

        this.evview = new ZrRect({
          type: 'evview',
          id: this.bgevid,
          zlevel: 1,
          z: 1,
          cursor: 'default',
          style: {
            opacity: 0
          },
          shape: {
            x: -width * 2,
            y: -height * 2,
            width: width * 6,
            height: height * 6
          },
          draggable: this._config.draggable
        }); //图形加入到zrende管理中

        zrender.add(this.evview);
      }
      /**
       * 添加全局拖拽事件,调用view的refresh,刷新所有的点数据的坐标
       * 点的updateview  会触发线的 updateview
       *          会触发group的updateview
       *          会触发group的group的updateview
       */

    }, {
      key: "addGlobalDragEvent",
      value: function addGlobalDragEvent() {
        var _this = this;

        if (this.evview) {
          var _view = this._view;

          this.evview.drift = function (dx, dy) {
            if (!_this._view.draggableFlag) {
              return;
            }

            _view.refresh(dx, dy);
          };
        }
      }
      /**
       * 添加全局缩放事件,支持按照鼠标位置进行缩放
       */

    }, {
      key: "addGlobalScaleEvent",
      value: function addGlobalScaleEvent() {
        var _this2 = this;

        //如果配置了roam缩放配置,则添加鼠标滚轮事件
        if (this.evview) {
          var zrender = this._zrender;
          var _view = this._view;
          this.evview.on('mousewheel', function (e) {
            if (!_this2._view.roamFlag) {
              return;
            }

            e.stop();
            var originX = e.event.offsetX;
            var originY = e.event.offsetY; //有配置缩放,并且按下了alt键

            var wheelDelta = e.wheelDelta; // wheelDelta maybe -0 in chrome mac.

            if (wheelDelta === 0) {
              return;
            }

            var factor = 1.1;
            var scaleDelta = wheelDelta > 0 ? factor : 1 / factor; //记录缩放值数据

            if (scaleDelta < 1 && _this2.scale < _this2.minScale) {
              return;
            } else {
              _this2.scale = _this2.scale * scaleDelta;
            }

            var allShapesList = zrender.storage.getDisplayList(true, true);
            allShapesList.forEach(function (el) {
              //有model的代表是 utopo管理的图形.因为线有可能是组管理的,所以要判断如有
              //el有parent,则el.parent必须有model对象
              if (el.model || el.parent && el.parent.model) {
                _this2.scaleEvent(el, [0, 0], [scaleDelta, scaleDelta], [originX, originY]);
              }
            });
            zrender.refresh();

            _view.clearZrAnimationLayer();
          });
        }
      }
    }, {
      key: "scaleEvent",
      value: function scaleEvent(root, positionDeltas, scaleDeltas, origin) {
        this.rawTransformable.scale = root.scale.slice();
        this.rawTransformable.position = root.position.slice();
        this.rawTransformable.origin = root.origin && root.origin.slice();
        this.rawTransformable.rotation = root.rotation;
        this.roamTransformable.scale = scaleDeltas;
        this.roamTransformable.origin = origin;
        this.roamTransformable.position = positionDeltas;
        this.roamTransformable.updateTransform();
        this.rawTransformable.updateTransform();
        copy(root.transform || (root.transform = []), this.rawTransformable.transform || create());
        root.decomposeTransform();
        root.dirty(true);
      } //手动调用缩放事件

    }, {
      key: "scaleTo",
      value: function scaleTo(toScale) {
        var _this3 = this;

        if (toScale < this.minScale) {
          toScale = this.minScale;
        } //如果配置了roam缩放配置,则添加鼠标滚轮事件


        var zrender = this._zrender;
        var originX = zrender.getWidth() / 2;
        var originY = zrender.getHeight() / 2; //按照当前缩放比例和要求比例进行计算放大倍数

        var scaleDelta = toScale / this.scale;
        this.scale = toScale;
        var allShapesList = zrender.storage.getDisplayList(true, true);
        allShapesList.forEach(function (el) {
          //有model的代表是 utopo管理的图形.因为线有可能是组管理的,所以要判断如有
          //el有parent,则el.parent必须有model对象
          if (el.model || el.parent && el.parent.model) {
            _this3.scaleEvent(el, [0, 0], [scaleDelta, scaleDelta], [originX, originY]);
          }
        });
        zrender.refresh();
      }
      /**
       * 添加鼠标移入效果,焦点事件
       */

    }, {
      key: "addGlobalMouseFocusEvent",
      value: function addGlobalMouseFocusEvent() {
        var _this4 = this;

        //增加鼠标焦点事件,如果配置了焦点选项则添加焦点事件触发
        //鼠标移入:关联对应点线高亮,其余的低亮
        //鼠标移出:恢复
        this._zrender.on("mouseover", function (e) {
          if (!_this4._view.focusFlag) {
            return;
          }

          var target = e.target;

          if (e.event.button !== 1 && !_this4._view._focus) {
            if (target && target.model && !(target.model instanceof Group$1)) {
              var allModels = _this4._view._topo._storage.getIndependentDataByModel(target.model);

              allModels.forEach(function (model) {
                model.focus(false);
              });

              _this4._view.clearZrAnimationLayer();
            }
          }
        });

        this._zrender.on("mouseout", function (e) {
          if (!_this4._view.focusFlag) {
            return;
          }

          var target = e.target;

          if (e.event.button !== 1 && !_this4._view._focus) {
            if (target && target.model && !(target.model instanceof Group$1)) {
              var allModels = _this4._view._topo._storage.getAllObjects();

              allModels.forEach(function (model) {
                model.focus(true);
              });

              _this4._view.clearZrAnimationLayer();
            }
          }
        });
      }
      /**
       * 全局绑定鼠标点击事件.
       * click:function(model){
       *     //model为modelElement对象
       * }
       * 实现原理,因为zrender只提供了单击事件.
       * 所以要做双击事件需要在点击事件中判断,间隔时间300毫秒内是否再次点击
       */

    }, {
      key: "addGlobalMouseClickEvent",
      value: function addGlobalMouseClickEvent() {
        var _this5 = this;

        //获取单击,双击配置
        var clickEvent = this._config.onclick;
        var dblclickEvent = this._config.ondblclick; //绑定默认空方法

        if (!isFunction(clickEvent)) {
          clickEvent = function clickEvent(e) {//console.log(1);
          };
        }

        if (!isFunction(dblclickEvent)) {
          dblclickEvent = function dblclickEvent(e) {//console.log(2);
          };
        } //点击计数


        this.clickNum = 0; //点击定时器

        this.clickTimer = null; //绑定全局点击事件

        this._zrender.on("click", function (e) {
          var target = e.target; //如果点击的shape对象上没有绑定的model的话则退出.

          if (!target || !target.model) {
            sessionStorage.setItem('isSelected','false')
            return;
          } //获取到点击对象的model对象


          var model = target.model;
          _this5.clickNum += 1; //触发双击事件

          if (_this5.clickNum === 2) {
            _this5.clickNum = 0;
            clearTimeout(_this5.clickTimer);

            if (model.ondblclick && !model.silent) {
              model.ondblclick(e);
            } else {
              //全局事件
              dblclickEvent(e, model);
            }
          } //触发单击事件


          if (_this5.clickNum === 1) {
            _this5.clickTimer = setTimeout(function () {
              if (model.onclick && !model.silent) {
                model.onclick(e);
              } else {
                clickEvent(e, model);
              }

              _this5.clickNum = 0;
            }, 400);
          } //清空计数器为0


          if (_this5.clickNum >= 2) {
            _this5.clickNum = 0;
          }
        });
      }
    }, {
      key: "addGlobalMouseClickEventDefault",
      value: function addGlobalMouseClickEventDefault() {
        var _this6 = this;

        this._zrender.on("click", function (e) {
          if (_this6._view._focus) {
            _this6._view._focus = false;

            var allModels = _this6._view._topo._storage.getIndependentDataByModelIds([]);

            allModels.forEach(function (model) {
              model.focus(true);
              model.highlight(false);
            });

            _this6._view.clearZrAnimationLayer();
          }
        });
      }
    }]);

    return GlobalEvent;
  }();

  var Legend = /*#__PURE__*/function () {
    function Legend(legendConfig, logicTopoView) {
      _classCallCheck(this, Legend);

      //topo对象
      this._view = logicTopoView; //zr的group

      this.id = logicTopoView._dom.id + "_utopo_legend_" + guid(); //contextMenu配置

      this.config = legendConfig; //组

      this.viewGroup = null; //标题视图对象可以增加事件

      this.view = null; //背景视图对象

      this.backView = null; //contextMenu初始化

      this.init();
    }

    _createClass(Legend, [{
      key: "init",
      value: function init() {
        this.viewGroup = new Group();
        this.createDefaultOption();
        this.createLegendView();
        this.addDragEvent();
        this.addMouseEvent();

        this._view.add(this.viewGroup);
      }
      /**
       * 默认配置值对象数据
       */

    }, {
      key: "createDefaultOption",
      value: function createDefaultOption() {
        var defaultOption = {
          //默认图形层级在100
          zlevel: 100,
          left: 0,
          bottom: 0,
          padding: 5,
          draggable: false,
          title: {
            h: 30,
            textFill: '#000',
            color: '#30B3CA'
          },
          back: {
            color: '#000'
          },
          item: {
            imageWidth: 16,
            rectWidth: 30,
            rectHeight: 10,
            textFill: '#000'
          }
        }; //合并属性

        merge(defaultOption, this.config, true);
        this.config = defaultOption;
      }
      /**
       * 清空的方法
       */

    }, {
      key: "clear",
      value: function clear() {
        this.viewGroup.removeAll();

        this._view.remove(this.viewGroup);
      }
      /**
       * 创建legend视图
       */

    }, {
      key: "createLegendView",
      value: function createLegendView() {
        createLegendRect(this);
        createLegendTitle(this);
        createLegendData(this);
        this.correctPosition();
      }
      /**
       * 校验位置,根据配置
       */

    }, {
      key: "correctPosition",
      value: function correctPosition() {
        var config = this.config;
        var rect = this.viewGroup.getBoundingRect(); //加上内边距

        rect.width += config.padding;
        rect.height += config.padding; //修改title大小

        this.view.attr('shape', {
          width: rect.width
        }); //修改背景大小

        this.backView.attr('shape', {
          width: rect.width,
          height: rect.height
        }); //修改组的位置,保证所有的图例都能看到

        var zHeight = this._view._zrender.getHeight();

        var left = config.left;
        var bottom = config.bottom;
        var position = [left, zHeight - rect.height - bottom];
        this.viewGroup.attr('position', position);
      }
      /**
       * 增加图例拖拽事件
       */

    }, {
      key: "addDragEvent",
      value: function addDragEvent() {
        var _this = this;

        this.view.drift = function (dx, dy) {
          _this.viewGroup.drift(dx, dy);
        };
      }
      /**
       * 增加鼠标点击事件,可以增加图例的放大缩小事件。
       */

    }, {
      key: "addMouseEvent",
      value: function addMouseEvent() {
        this.view.on('click', function (e) {});
      }
    }]);

    return Legend;
  }();
  /**
   * 创建图例标题
   */


  function createLegendTitle(model) {
    var config = model.config; //获取配置否则用默认

    var h = config.title.h;
    var textColor = config.title.textFill;
    var color = config.title.color; //生成title矩形对象

    model.view = new ZrRect({
      position: [0, 0],
      zlevel: config.zlevel,
      shape: {
        x: 0,
        y: 0,
        width: 0,
        //后续会变
        height: h,
        r: [5, 5, 0, 0]
      },
      style: {
        text: config.name,
        textFill: textColor,
        fill: color
      },
      hoverable: false,
      draggable: config.draggable,
      clickable: false
    }); //图形加入组中

    model.viewGroup.add(model.view);
  }
  /**
   * 增加背景视图对象
   */


  function createLegendRect(model) {
    var config = model.config;
    var h = config.title.h;
    model.backView = new ZrRect({
      zlevel: config.zlevel,
      position: [0, 0],
      shape: {
        x: 0,
        y: 0,
        width: 0,
        height: h,
        r: [5, 5, 5, 5]
      },
      style: {
        fill: config.back ? config.back.color ? config.back.color : null : null
      },
      hoverable: false,
      draggable: false,
      clickable: false
    });
    model.viewGroup.add(model.backView);
  }
  /**
   * 创建legend的data区域数据
   */


  function createLegendData(model) {
    var config = model.config;
    var viewGroup = model.viewGroup;
    var data = config.data; //数据对象偏移量

    var offset = [config.padding, config.title.h + config.padding];

    if (data && data.length > 0) {
      data.forEach(function (item) {
        var itemTemp = clone(item);
        itemTemp.x = item.x + offset[0];
        itemTemp.y = item.y + offset[1]; //创建图片

        var items = [];

        if (item.src) {
          items = createLegendImgItemData(itemTemp, config);
        } else if (item.color && item.w && item.h) {
          items = createLegendRectItemData(itemTemp, config);
        }

        items.forEach(function (i) {
          viewGroup.add(i);
        });
      });
    }
    /**
     * 创建图例Item为图片
     */


    function createLegendImgItemData(item, config) {
      //图例图片大小16*16
      var imgWidth = config.item.imageWidth;
      var imgItem = new ZImage({
        zlevel: config.zlevel,
        position: [0, 0],
        style: {
          x: item.x,
          y: item.y,
          width: imgWidth,
          height: imgWidth,
          image: item.src
        },
        hoverable: false,
        draggable: false,
        clickable: false
      });
      var imgItemLabel = new Text({
        zlevel: config.zlevel,
        position: [0, 0],
        style: {
          x: item.x + imgWidth + 4,
          y: item.y + 2,
          height: 16,
          text: item.label,
          textFill: config.item.textFill
        },
        hoverable: false,
        draggable: false,
        clickable: false
      });
      return [imgItem, imgItemLabel];
    }
    /**
     * 创建图例Item为Rect矩形
     */


    function createLegendRectItemData(item, config) {
      var w = item.w ? item.w : config.item.rectWidth;
      var h = item.h ? item.h : config.item.rectHeight;
      var imgItem = new ZrRect({
        zlevel: config.zlevel,
        position: [0, 0],
        shape: {
          x: item.x,
          y: item.y + 3,
          width: w,
          height: h
        },
        style: {
          fill: item.color
        },
        hoverable: false,
        draggable: false,
        clickable: false
      });
      var imgItemLabel = new Text({
        zlevel: config.zlevel,
        position: [0, 0],
        style: {
          x: item.x + item.w + 4,
          y: item.y + 2,
          height: 16,
          text: item.label,
          textFill: config.item.textFill
        },
        hoverable: false,
        draggable: false,
        clickable: false
      });
      return [imgItem, imgItemLabel];
    }
  }

  /**
   * utopo的绘图层，调用zrender 绘制图
   * @author zhangnt
   */

  var UTopoView = /*#__PURE__*/function () {
    function UTopoView(utopo) {
      _classCallCheck(this, UTopoView);

      this._topo = utopo;
      this._config = utopo._config;
      this._zrender = null;
      this._dom = this._config.dom; //调用初始化方法

      this._grid = null; //鼠标右键

      this._contextMenu = null; //鼠标滑过提示

      this._tooltip = null; //记录全局事件对象

      this._globalEvent = null; //图例对象canvas绘图

      this._legend = new Map(); //工具箱

      this._toolbox = null; //当前是否有高亮

      this._focus = false;
      this.focusFlag = utopo._config.hasOwnProperty('focus') && utopo._config.focus === false ? false : true;
      this.roamFlag = utopo._config.hasOwnProperty('roam') && utopo._config.roam === false ? false : true;
      this.draggableFlag = utopo._config.hasOwnProperty('draggable') && utopo._config.draggable === false ? false : true;
      this.init();
    }

    _createClass(UTopoView, [{
      key: "init",
      value: function init$1() {
        if (!this._dom) {
          console.warn("no dom element");
          return;
        } //初始化zrender


        this._zrender = init(this._dom);

        this._zrender.setBackgroundColor('transparent');

        this._zrender.configLayer(GlobalConfig.level.ANIMATION_LEVEL, {
          motionBlur: true,
          lastFrameAlpha: 0.7
        });

        this._zrender.configLayer(GlobalConfig.level.POINT_ANIMATION_LEVEL, {
          motionBlur: true,
          lastFrameAlpha: 0.9
        }); //添加zrende背景图片,和事件处理


        this.addGridView(); //添加tooltip鼠标滑过提示

        this.addTooltip(this._config.tooltip); //添加contextMenu右键菜单

        this.addContextMenu(this._config.contextMenu); //添加全局事件类型

        this.addGlobalEvent(); //添加图例数据

        this.addLegend(this._config.legend); //添加工具箱

        this.addToolBox(this._config.toolbox);
      }
      /**
       * 调用zrender的增加图形的方法
       * @param el
       */

    }, {
      key: "add",
      value: function add(el) {
        this._zrender.add(el);
      }
    }, {
      key: "remove",
      value: function remove(el) {
        this._zrender.remove(el);
      }
      /**
       * 添加背景grid,用于事件添加,全局拖拽,全局缩放
       */

    }, {
      key: "addGridView",
      value: function addGridView() {
        var gridConfig = this._config.grid ? this._config.grid : {};
        this._grid = new Grid(gridConfig, this);
      }
      /**
       * 配置右键菜单
       * @param menuConfig
       */

    }, {
      key: "addContextMenu",
      value: function addContextMenu(menuConfig) {
        if (menuConfig) {
          this._config.contextMenu = menuConfig;
        }

        if (this._config.contextMenu) {
          this._contextMenu = new ContextMenu(this._config.contextMenu, this);
        }
      }
      /**
       * 添加配置的鼠标滑过提示
       * @param tooltipConfig
       */

    }, {
      key: "addTooltip",
      value: function addTooltip(tooltipConfig) {
        if (tooltipConfig) {
          this._config.tooltip = tooltipConfig;
        }

        if (this._config.tooltip) {
          this._tooltip = new Tooltip(this._config.tooltip, this);
        }
      }
      /**
       * 添加全局事件对象
       */

    }, {
      key: "addGlobalEvent",
      value: function addGlobalEvent() {
        this._globalEvent = new GlobalEvent(this);
      }
      /**
       * 添加图例,支持多个,按照title区别
       */

    }, {
      key: "addLegend",
      value: function addLegend(legendConfigs) {
        var _this = this;

        if (!legendConfigs) {
          return;
        }

        if (!(legendConfigs instanceof Array)) {
          legendConfigs = [legendConfigs];
        }

        legendConfigs.forEach(function (legendConfig) {
          var name = legendConfig.name;

          if (!_this._legend.has(name)) {
            var legend = new Legend(legendConfig, _this);

            _this._legend.set(name, legend);
          }
        });
      }
      /**
       * 重新加载已经存在的legend数据
       */

    }, {
      key: "refreshLegend",
      value: function refreshLegend() {
        var configs = [];

        this._legend.forEach(function (legend) {
          configs.push(legend.config);
        });

        this.removeLegend();
        this.addLegend(configs);
      }
    }, {
      key: "removeLegend",
      value: function removeLegend(name) {
        if (name) {
          var legend = this._legend.get(name);

          if (legend) {
            legend.clear();
          }

          legend = null;

          this._legend["delete"](name);
        } else {
          this._legend.forEach(function (legend) {
            legend.clear();
          });

          this._legend = new Map();
        }
      }
      /**
       * 增加工具箱配置项
       * @param {} toolboxConfigs 
       */

    }, {
      key: "addToolBox",
      value: function addToolBox(toolboxConfig) {
        if (!toolboxConfig) {
          return;
        }

        this._toolbox = new ToolBox(toolboxConfig, this);
      }
    }, {
      key: "removeToolBox",
      value: function removeToolBox() {
        this._toolbox.clear();

        this._toolbox = null;
      }
      /**
       * @param dx
       * @param dy
       * view视图层的刷新,获取_stroage中的所有的点数据进行刷新位置,
       * 点数据的刷新触发线、集合的刷新
       */

    }, {
      key: "refresh",
      value: function refresh(dx, dy) {
        if (!dx && !dy) {
          dx = 0;
          dy = 0;
        }

        var _allPoints = this._topo._storage.getAllPoints();

        _allPoints.forEach(function (point) {
          point.updateView(dx, dy);
        });
      }
      /**
       * 重置大小,暂未提供
       */

    }, {
      key: "resize",
      value: function resize() {
        var width = this._zrender.getWidth();

        var height = this._zrender.getHeight();

        this._zrender.resize();

        var nowWidth = this._zrender.getWidth();

        var nowHeight = this._zrender.getHeight();

        var allPoints = this._topo._storage.getAllPoints();

        allPoints.forEach(function (el) {
          var center = el.getCenterPoint();
          var x = center.x / width * nowWidth;
          var y = center.y / height * nowHeight;
          el.moveTo(x, y);
        });

        if (this._grid) {
          //修改背景
          this._grid.resize();
        }

        this.refreshLegend();
        this.refresh();
      }
    }, {
      key: "getWidth",
      value: function getWidth() {
        return this._zrender.getWidth();
      }
    }, {
      key: "getHeight",
      value: function getHeight() {
        return this._zrender.getHeight();
      }
      /**
       * 清空绘图,zrender对象删除
       */

    }, {
      key: "dispose",
      value: function dispose() {
        this._zrender.dispose();
      }
      /**
       * 清空动画层级的上一帧效果
       */

    }, {
      key: "clearZrAnimationLayer",
      value: function clearZrAnimationLayer() {
        //先设置为false,清空,再设置为true
        //点的涟漪效果层级
        this._zrender.configLayer(GlobalConfig.level.POINT_ANIMATION_LEVEL, {
          motionBlur: false
        });

        this._zrender.painter.clearLayer(GlobalConfig.level.POINT_ANIMATION_LEVEL);

        this._zrender.configLayer(GlobalConfig.level.POINT_ANIMATION_LEVEL, {
          motionBlur: true
        }); //线的动画层级


        this._zrender.configLayer(GlobalConfig.level.ANIMATION_LEVEL, {
          motionBlur: false
        });

        this._zrender.painter.clearLayer(GlobalConfig.level.ANIMATION_LEVEL);

        this._zrender.configLayer(GlobalConfig.level.ANIMATION_LEVEL, {
          motionBlur: true
        });
      }
    }, {
      key: "autoCenter",
      value: function autoCenter() {
        var width = this.getWidth();
        var height = this.getHeight();
        var x0 = width / 2;
        var y0 = height / 2;

        var points = this._topo.getAllPoints();

        var minX,
            minY,
            maxX,
            maxY = null; //循环获取点的坐标

        points.forEach(function (point) {
          var pc = point.getCenterPoint();
          var x = pc.x;
          var y = pc.y;

          if (minX == null) {
            minX = x;
            minY = y;
            maxX = x;
            maxY = y;
          }

          if (x < minX) {
            minX = x;
          }

          if (x > maxX) {
            maxX = x;
          }

          if (y < minY) {
            minY = y;
          }

          if (y > maxY) {
            maxY = y;
          }
        });
        var dx = x0 - (maxX + minX) / 2;
        var dy = y0 - (maxY + minY) / 2; //移动点

        this.refresh(dx, dy);
      }
    }, {
      key: "autoAdapt",
      value: function autoAdapt() {
        this._globalEvent.scaleTo(1);

        var width = this.getWidth();
        var height = this.getHeight();
        var x0 = width / 2;
        var y0 = height / 2;

        var points = this._topo.getAllPoints();

        var minX,
            minY,
            maxX,
            maxY = null; //循环获取点的坐标

        points.forEach(function (point) {
          var pc = point.getCenterPoint();
          var x = pc.x;
          var y = pc.y;

          if (minX == null) {
            minX = x;
            minY = y;
            maxX = x;
            maxY = y;
          }

          if (x < minX) {
            minX = x;
          }

          if (x > maxX) {
            maxX = x;
          }

          if (y < minY) {
            minY = y;
          }

          if (y > maxY) {
            maxY = y;
          }
        });
        var dx = x0 - (maxX + minX) / 2;
        var dy = y0 - (maxY + minY) / 2; //移动点

        this.refresh(dx, dy);
        var w = maxX - minX;
        var h = maxY - minY;
        var scale1 = w / width;
        var scale2 = h / height;
        var scale = scale1 > scale2 ? scale1 : scale2; //说明需要进行缩放

        if (scale > 1) {
          this._globalEvent.scaleTo(1 / scale);
        }
      }
    }, {
      key: "focus",
      value: function focus(ids) {
        var allModels = this._topo._storage.getIndependentDataByModelIds(ids);

        var allModelsD = this._topo._storage.getDependentDataByModelIds(ids); //判断是否允许焦点操作


        if (this.focusFlag) {
          if (allModelsD != null && allModelsD.length > 0) {
            allModelsD.forEach(function (model) {
              model.highlight(1);
              model.focus(true);
            });
          }

          if (allModels != null && allModels.length > 0) {
            allModels.forEach(function (model) {
              model.highlight(false);
              model.focus(false);
            });
            this._focus = true;
            this.clearZrAnimationLayer();
          }
        } else {
          //不需要焦点则只需要高亮
          if (allModelsD != null && allModelsD.length > 0) {
            allModelsD.forEach(function (model) {
              model.highlight(1);
            });
          }

          if (allModels != null && allModels.length > 0) {
            allModels.forEach(function (model) {
              model.highlight(false);
            });
            this._focus = true;
            this.clearZrAnimationLayer();
          }
        }
      }
      /**
       * 获取当前缩放scale数据
       */

    }, {
      key: "getScale",
      value: function getScale() {
        var scale = this._globalEvent.getScale(); //因为计算可能有点点误差,所以不超过0.05的就算1恢复


        if (Math.abs(1 - scale) <= 0.05) {
          return 1;
        } else {
          return scale;
        }
      }
      /**
       * 针对当前缩放大小增加减小
       */

    }, {
      key: "scaleAdd",
      value: function scaleAdd(num) {
        var scale = this._globalEvent.getScale();

        scale += num;
        this.scaleTo(scale);
      }
      /**
       * 针对当前缩放大小，进行指定缩放大小缓缓
       */

    }, {
      key: "scaleTo",
      value: function scaleTo(scale) {
        this._globalEvent.scaleTo(scale);
      }
    }]);

    return UTopoView;
  }();

  var SET = 'Set';

  // 23.2 Set Objects
  var es6_set = _collection(SET, function (get) {
    return function Set() { return get(this, arguments.length > 0 ? arguments[0] : undefined); };
  }, {
    // 23.2.3.1 Set.prototype.add(value)
    add: function add(value) {
      return _collectionStrong.def(_validateCollection(this, SET), value = value === 0 ? 0 : value, value);
    }
  }, _collectionStrong);

  /**
   * 圆形
   * @module zrender/shape/Circle
   */

  var ZrCircle = Path.extend({

      type: 'circle',

      shape: {
          cx: 0,
          cy: 0,
          r: 0
      },


      buildPath: function (ctx, shape, inBundle) {
          // Better stroking in ShapeBundle
          // Always do it may have performence issue ( fill may be 2x more cost)
          if (inBundle) {
              ctx.moveTo(shape.cx + shape.r, shape.cy);
          }
          // else {
          //     if (ctx.allocate && !ctx.data.length) {
          //         ctx.allocate(ctx.CMD_MEM_SIZE.A);
          //     }
          // }
          // Better stroking in ShapeBundle
          // ctx.moveTo(shape.cx + shape.r, shape.cy);
          ctx.arc(shape.cx, shape.cy, shape.r, 0, Math.PI * 2, true);
      }
  });

  /**
   * 圆环
   * @module zrender/graphic/shape/Ring
   */

  var ZrRing = Path.extend({

      type: 'ring',

      shape: {
          cx: 0,
          cy: 0,
          r: 0,
          r0: 0
      },

      buildPath: function (ctx, shape) {
          var x = shape.cx;
          var y = shape.cy;
          var PI2 = Math.PI * 2;
          ctx.moveTo(x + shape.r, y);
          ctx.arc(x, y, shape.r, 0, PI2, false);
          ctx.moveTo(x + shape.r0, y);
          ctx.arc(x, y, shape.r0, 0, PI2, true);
      }
  });

  // 21.2.5.3 get RegExp.prototype.flags()
  if (_descriptors && /./g.flags != 'g') _objectDp.f(RegExp.prototype, 'flags', {
    configurable: true,
    get: _flags
  });

  var TO_STRING = 'toString';
  var $toString = /./[TO_STRING];

  var define = function (fn) {
    _redefine(RegExp.prototype, TO_STRING, fn, true);
  };

  // 21.2.5.14 RegExp.prototype.toString()
  if (_fails(function () { return $toString.call({ source: 'a', flags: 'b' }) != '/a/b'; })) {
    define(function toString() {
      var R = _anObject(this);
      return '/'.concat(R.source, '/',
        'flags' in R ? R.flags : !_descriptors && R instanceof RegExp ? _flags.call(R) : undefined);
    });
  // FF44- RegExp#toString has a wrong name
  } else if ($toString.name != TO_STRING) {
    define(function toString() {
      return $toString.call(this);
    });
  }

  var DateProto = Date.prototype;
  var INVALID_DATE = 'Invalid Date';
  var TO_STRING$1 = 'toString';
  var $toString$1 = DateProto[TO_STRING$1];
  var getTime = DateProto.getTime;
  if (new Date(NaN) + '' != INVALID_DATE) {
    _redefine(DateProto, TO_STRING$1, function toString() {
      var value = getTime.call(this);
      // eslint-disable-next-line no-self-compare
      return value === value ? $toString$1.call(this) : INVALID_DATE;
    });
  }

  var PointProxy = /*#__PURE__*/function () {
    function PointProxy() {
      _classCallCheck(this, PointProxy);
    }

    _createClass(PointProxy, [{
      key: "getShapeDefaultOption",
      value: function getShapeDefaultOption(model) {
        var option = {
          id: model.id.toString(),
          mtype: model.mtype,
          zlevel: model.zlevel,
          position: [0, 0],
          style: {
            fill: model.style.color ? model.style.color : PointConfig["default"].color,
            text: model.name ? model.name : model.style.text ? model.style.text : '',
            fontSize: model.style.fontSize ? model.style.fontSize : PointConfig["default"].fontSize,
            fontFamily: model.style.fontFamily ? model.style.fontFamily : PointConfig["default"].fontFamily,
            textPosition: model.style.textPosition ? model.style.textPosition : PointConfig["default"].textPosition,
            textFill: model.style.textFill ? model.style.textFill : PointConfig["default"].textFill,
            textAlign: model.style.textAlign ? model.style.textAlign : null,
            transformText: true
          },
          draggable: model.draggable
        };

        if (model.z != null && !isNaN(model.z)) {
          option.z = model.z;
        }

        return option;
      }
      /**
       * 创建文字标记：利用绘制一条同样位置的看不见的线的text配置生成文字效果
       */

    }, {
      key: "createPointSign",
      value: function createPointSign(model) {
        var rect = model.getCenterPoint();
        var signViews = [];
        var signs = model.sign;
        signs.forEach(function (sign) {
          //直接在制定位置写文字
          var x = rect.x;
          var y = rect.y;
          var p = sign.p ? sign.p : [0, 0];
          var lineOption = {
            zlevel: model.zlevel + 1,
            z: 10000,
            //记录配置对象
            style: {
              x: x + p[0],
              y: y + p[1],
              text: sign.text,
              stroke: null,
              lineWidth: 0,
              textBorderWidth: 0,
              textBorderRadius: 10,
              textWidth: 12,
              fontSize: sign.fontSize ? sign.fontSize : PointConfig.sign.fontSize,
              fontFamily: model.style.fontFamily ? model.style.fontFamily : PointConfig["default"].fontFamily,
              textLineHeight: sign.textLineHeight ? sign.textLineHeight : PointConfig.sign.textLineHeight,
              textFill: sign.color ? sign.color : PointConfig.sign.color,
              textBackgroundColor: sign.backgroundColor ? sign.backgroundColor : PointConfig.sign.backgroundColor,
              textPadding: sign.padding ? sign.padding : PointConfig.sign.padding,
              textAlign: sign.align ? sign.align : PointConfig.sign.textAlign
            },
            draggable: false
          };
          var zText = new Text(lineOption);
          signViews.push(zText);
        });
        return signViews;
      }
      /**
       * 创建点上的symbol图片对象,
       * 可配置：
       *      位置
       *      图形
       *      单击事件
       */

    }, {
      key: "createSymbolView",
      value: function createSymbolView(model) {
        var _this = this;

        //获取点的区域位置
        var rect = model.getInRect(); //返回数组对象

        var symbolViews = [];
        model.symbol.forEach(function (s) {
          try {
            var style = _this.getSymbolStyle(s);

            var style1 = _this.getSymbolPosition(rect, s);

            merge(style, style1, true); //生成图标

            var imageOption = {
              gtype: 'symbol',
              gdata: s,
              zlevel: model.zlevel + 1,
              position: [0, 0],
              style: style,
              draggable: false
            };
            var symbolImg = new ZImage(imageOption);
            symbolImg.on("click", function () {
              s.onclick(model);
            });
            symbolViews.push(symbolImg);
          } catch (e) {
            console.warn(e);
          }
        });
        return symbolViews;
      }
      /**
       * 获取图标位置,默认右上角
       * @param {} rect 
       * @param {*} s 
       */

    }, {
      key: "getSymbolPosition",
      value: function getSymbolPosition(rect, s) {
        //计算图标位置
        var x = rect.x,
            y = rect.y;
        var width = s.width ? s.width : 15;
        var height = s.height ? s.height : 15;
        var dx = rect.width - width / 3 * 2;
        var dy = -height / 3;
        var position = s.position;

        if (position instanceof Array) {
          dx = position[0];
          dy = position[1];
        }

        x = x + dx;
        y = y + dy;
        return {
          x: x,
          y: y,
          width: width,
          height: height
        };
      }
      /**
       * symbol的配置项目
       * @param {} s 
       */

    }, {
      key: "getSymbolStyle",
      value: function getSymbolStyle(s) {
        var style = {
          fill: PointConfig["default"].color,
          text: s.text ? s.text : null,
          fontSize: s.fontSize ? s.fontSize : PointConfig["default"].fontSize,
          fontFamily: s.fontFamily ? s.fontFamily : PointConfig["default"].fontFamily,
          textPosition: 'inside',
          textFill: s.textFill ? s.textFill : PointConfig["default"].textFill,
          transformText: true
        };
        return style;
      }
      /**
       * 创建高亮圈
       * @param pointView
       */

    }, {
      key: "createHighLightView",
      value: function createHighLightView(model, pointView) {
        if (model.hstyle.image) {
          //如果hstyle.image有值表示高亮是切换图片
          return null;
        }

        var x, y, r;
        var hstyle = model.hstyle;
        var r0 = hstyle.r ? hstyle.r : PointConfig.emphasis.r;
        var scale = pointView.scale;

        if (pointView instanceof ZImage) {
          x = (pointView.style.x + pointView.style.width / 2) * scale[0];
          y = (pointView.style.y + pointView.style.height / 2) * scale[1];
          r = (pointView.style.width / 2 + r0) * scale[0];
        } else {
          x = (pointView.shape.x + pointView.shape.width / 2) * scale[0];
          y = (pointView.shape.y + pointView.shape.height / 2) * scale[1];
          r = (pointView.shape.width / 2 + r0) * scale[0];
        }

        var hCircleOption = {
          zlevel: pointView.zlevel - 1,
          z: 1000,
          position: [0, 0],
          shape: {
            cx: x,
            cy: y,
            r: r
          },
          invisible: true,
          style: {
            stroke: hstyle.stroke ? hstyle.stroke : PointConfig.emphasis.stroke,
            lineWidth: hstyle.lineWidth ? hstyle.lineWidth : PointConfig.emphasis.lineWidth,
            fill: null
          },
          draggable: false
        };
        return new ZrCircle(hCircleOption);
      }
      /**
       * 创建简单涟漪效果
       * @param model
       */

    }, {
      key: "createSimpleFlickerAnimation",
      value: function createSimpleFlickerAnimation(model) {
        var r = PointConfig["default"].r;

        if (model.flicker.hasOwnProperty('r')) {
          r = model.flicker.r;
        } else if (model.style.hasOwnProperty('r')) {
          r = model.style.r;
        } else if (model.style.hasOwnProperty('width')) {
          r = model.style.width / 2;
        }

        var option = {
          amtype: 'point_flicker',
          zlevel: GlobalConfig.level.POINT_ANIMATION_LEVEL,
          position: [0, 0],
          shape: {
            cx: isNaN(model.style.x) ? 0 : model.style.x,
            cy: isNaN(model.style.y) ? 0 : model.style.y,
            r: r
          },
          style: {
            fill: null,
            stroke: model.flicker.color ? model.flicker.color : PointConfig.flicker.color,
            lineWidth: 2
          },
          draggable: false
        };
        var time = 1000;
        var zrCircle = new ZrCircle(option);
        zrCircle.animate('style', true).when(time, {
          opacity: 0
        }).start();
        zrCircle.animate('shape', true).when(time, {
          r: r + 12
        }).start();
        return zrCircle;
      }
    }]);

    return PointProxy;
  }();

  /**
   * utopo 图片点代理类
   * @author zhangnt
   */

  var PointImgProxy = /*#__PURE__*/function (_PointProxy) {
    _inherits(PointImgProxy, _PointProxy);

    var _super = _createSuper(PointImgProxy);

    function PointImgProxy() {
      _classCallCheck(this, PointImgProxy);

      return _super.call(this);
    } //创建主要视图对象


    _createClass(PointImgProxy, [{
      key: "createView",
      value: function createView(model) {
        var defaultOption = this.getShapeDefaultOption(model);
        var imageOption = {
          style: {
            image: model.style.image,
            x: isNaN(model.style.x) ? 0 : model.style.x - (model.style.width ? model.style.width / 2 : PointConfig["default"].width / 2),
            y: isNaN(model.style.y) ? 0 : model.style.y - (model.style.height ? model.style.height / 2 : PointConfig["default"].height / 2),
            width: isNaN(model.style.width) ? PointConfig["default"].width : model.style.width,
            height: isNaN(model.style.height) ? PointConfig["default"].height : model.style.height
          }
        };
        merge(imageOption, defaultOption);
        var img = new ZImage(imageOption);
        return img;
      }
      /**
       * 生成点组内所有包含元素的更新位置坐标的方法
       */

    }, {
      key: "updateEl",
      value: function updateEl(el, dx, dy) {
        if (el instanceof ZImage) {
          var x = el.style.x;
          var y = el.style.y;
          var scale = el.scale;
          el.attr("style", {
            x: x + dx / scale[0],
            y: y + dy / scale[1]
          });

          if (el.clipPath) {
            var clipPath = el.clipPath;

            if (clipPath instanceof ZrCircle) {
              var cx = clipPath.shape.cx;
              var cy = clipPath.shape.cy;
              clipPath.attr("shape", {
                cx: cx + dx / scale[0],
                cy: cy + dy / scale[1]
              });
            }
          }
        } //点和高亮层对象的坐标变换


        if (el instanceof ZrCircle || el instanceof ZrRing) {
          var _cx = el.shape.cx;
          var _cy = el.shape.cy;
          var _scale = el.scale;
          el.attr("shape", {
            cx: _cx + dx / _scale[0],
            cy: _cy + dy / _scale[1]
          });
        }

        if (el instanceof ZrRect) {
          var _x = el.shape.x;
          var _y = el.shape.y;
          var _scale2 = el.scale;
          el.attr("shape", {
            x: _x + dx / _scale2[0],
            y: _y + dy / _scale2[1]
          });
        } //如果是文字则代表为sign对象


        if (el instanceof Text) {
          var _x2 = el.style.x;
          var _y2 = el.style.y;
          var _scale3 = el.scale;
          el.attr("style", {
            x: _x2 + dx / _scale3[0],
            y: _y2 + dy / _scale3[1]
          });
        }
      }
    }, {
      key: "changeViewImg",
      value: function changeViewImg(view, model, flag) {
        var image = null;

        if (flag === 1) {
          image = model.hstyle.image;
        } else {
          image = model.style.image;
        }

        view.attr("style", {
          image: image
        });
      }
    }]);

    return PointImgProxy;
  }(PointProxy);

  /**
   * utopo 矩形点代理类
   * @author zhangnt
   */

  var PointRectProxy = /*#__PURE__*/function (_PointProxy) {
    _inherits(PointRectProxy, _PointProxy);

    var _super = _createSuper(PointRectProxy);

    function PointRectProxy() {
      _classCallCheck(this, PointRectProxy);

      return _super.call(this);
    } //创建主要视图对象


    _createClass(PointRectProxy, [{
      key: "createView",
      value: function createView(model) {
        var defaultOption = this.getShapeDefaultOption(model);

        if (model.mtype === GlobalConfig.mtype.POINT_CORE_CIR) {
          var cirOption = {
            shape: {
              cx: isNaN(model.style.x) ? 0 : model.style.x,
              cy: isNaN(model.style.y) ? 0 : model.style.y,
              r: isNaN(model.style.r) ? PointConfig["default"].r : model.style.r
            }
          };
          merge(cirOption, defaultOption);
          return new ZrCircle(cirOption);
        } else {
          var rectOption = {
            shape: {
              x: isNaN(model.style.x) ? 0 : model.style.x - (model.style.width ? model.style.width / 2 : PointConfig["default"].width / 2),
              y: isNaN(model.style.y) ? 0 : model.style.y - (model.style.width ? model.style.width / 2 : PointConfig["default"].height / 2),
              width: isNaN(model.style.width) ? PointConfig["default"].width : model.style.width,
              height: isNaN(model.style.height) ? PointConfig["default"].height : model.style.height
            }
          };
          merge(rectOption, defaultOption);
          return new ZrRect(rectOption);
        }
      }
      /**
       * 生成点组内所有包含元素的更新位置坐标的方法
       */

    }, {
      key: "updateEl",
      value: function updateEl(el, dx, dy) {
        if (el instanceof ZImage) {
          var x = el.style.x;
          var y = el.style.y;
          var scale = el.scale;
          el.attr("style", {
            x: x + dx / scale[0],
            y: y + dy / scale[1]
          });

          if (el.clipPath) {
            var clipPath = el.clipPath;

            if (clipPath instanceof ZrCircle) {
              var cx = clipPath.shape.cx;
              var cy = clipPath.shape.cy;
              clipPath.attr("shape", {
                cx: cx + dx / scale[0],
                cy: cy + dy / scale[1]
              });
            }
          }
        } //点和高亮层对象的坐标变换


        if (el instanceof ZrCircle || el instanceof ZrRing) {
          var _cx = el.shape.cx;
          var _cy = el.shape.cy;
          var _scale = el.scale;
          el.attr("shape", {
            cx: _cx + dx / _scale[0],
            cy: _cy + dy / _scale[1]
          });
        }

        if (el instanceof ZrRect) {
          var _x = el.shape.x;
          var _y = el.shape.y;
          var _scale2 = el.scale;
          el.attr("shape", {
            x: _x + dx / _scale2[0],
            y: _y + dy / _scale2[1]
          });
        } //如果是文字则代表为sign对象


        if (el instanceof Text) {
          var _x2 = el.style.x;
          var _y2 = el.style.y;
          var _scale3 = el.scale;
          el.attr("style", {
            x: _x2 + dx / _scale3[0],
            y: _y2 + dy / _scale3[1]
          });
        }
      }
    }]);

    return PointRectProxy;
  }(PointProxy);

  /**
   * 直线
   * @module zrender/graphic/shape/Line
   */

  // Avoid create repeatly.
  var subPixelOptimizeOutputShape$1 = {};

  var ZrLine = Path.extend({

      type: 'line',

      shape: {
          // Start point
          x1: 0,
          y1: 0,
          // End point
          x2: 0,
          y2: 0,

          percent: 1
      },

      style: {
          stroke: '#000',
          fill: null
      },

      buildPath: function (ctx, shape) {
          var x1;
          var y1;
          var x2;
          var y2;

          if (this.subPixelOptimize) {
              subPixelOptimizeLine(subPixelOptimizeOutputShape$1, shape, this.style);
              x1 = subPixelOptimizeOutputShape$1.x1;
              y1 = subPixelOptimizeOutputShape$1.y1;
              x2 = subPixelOptimizeOutputShape$1.x2;
              y2 = subPixelOptimizeOutputShape$1.y2;
          }
          else {
              x1 = shape.x1;
              y1 = shape.y1;
              x2 = shape.x2;
              y2 = shape.y2;
          }

          var percent = shape.percent;

          if (percent === 0) {
              return;
          }

          ctx.moveTo(x1, y1);

          if (percent < 1) {
              x2 = x1 * (1 - percent) + x2 * percent;
              y2 = y1 * (1 - percent) + y2 * percent;
          }
          ctx.lineTo(x2, y2);
      },

      /**
       * Get point at percent
       * @param  {number} percent
       * @return {Array.<number>}
       */
      pointAt: function (p) {
          var shape = this.shape;
          return [
              shape.x1 * (1 - p) + shape.x2 * p,
              shape.y1 * (1 - p) + shape.y2 * p
          ];
      }
  });

  /**
   * utopo 线形点代理类
   * @author zhangnt
   */

  var PointLineProxy = /*#__PURE__*/function (_PointProxy) {
    _inherits(PointLineProxy, _PointProxy);

    var _super = _createSuper(PointLineProxy);

    function PointLineProxy() {
      _classCallCheck(this, PointLineProxy);

      return _super.call(this);
    } //创建主要视图对象


    _createClass(PointLineProxy, [{
      key: "createView",
      value: function createView(model) {
        var defaultOption = this.getShapeDefaultOption(model);

        if (model.style.image) {
          var imageOption = {
            zlevel: 99999,
            style: {
              image: model.style.image,
              height: 30,
              x: 0,
              y: model.style.y - 15,
              width: 100
            }
          };
          merge(defaultOption, imageOption, true);
          return new ZImage(defaultOption);
        } else {
          var lineOption = {
            zlevel: 99999,
            style: {
              lineWidth: 20,
              stroke: '#99D2FD'
            },
            shape: {
              x1: 0,
              y1: isNaN(model.style.y) ? 0 : model.style.y,
              x2: 100,
              y2: isNaN(model.style.y) ? 0 : model.style.y
            }
          };
          merge(defaultOption, lineOption, true);
          return new ZrLine(defaultOption);
        }
      }
      /**
       * 生成点组内所有包含元素的更新位置坐标的方法
       */

    }, {
      key: "updateEl",
      value: function updateEl(el, dx, dy) {
        if (el instanceof ZrLine) {
          var y1 = el.shape.y1;
          var y2 = el.shape.y2;
          var scale = el.scale;
          el.attr("shape", {
            y1: y1 + dy / scale[1],
            y2: y2 + dy / scale[1]
          });
        }

        if (el instanceof ZImage) {
          var y = el.style.y;
          var _scale = el.scale;
          el.attr("style", {
            y: y + dy / _scale[1]
          });
        }
      }
    }, {
      key: "updateElSize",
      value: function updateElSize(el, x1, x2) {
        if (el instanceof ZrLine) {
          var scale = el.scale;
          el.attr('shape', {
            x1: x1 - 20,
            x2: x2 + 20
          });
        }

        if (el instanceof ZImage) {
          var _scale2 = el.scale;
          el.attr('style', {
            x: x1 / _scale2[0] - 50,
            width: (x2 - x1) / _scale2[1] + 100
          });
        }
      }
    }, {
      key: "createHighLightView",
      value: function createHighLightView(pointView) {
        return null;
      }
    }, {
      key: "createSimpleFlickerAnimation",
      value: function createSimpleFlickerAnimation(model) {
        return null;
      }
    }, {
      key: "changeViewImg",
      value: function changeViewImg(view, model, flag) {
        var image = null;

        if (flag === 1) {
          image = model.hstyle.image;
        } else {
          image = model.style.image;
        }

        view.attr("style", {
          image: image
        });
      }
    }]);

    return PointLineProxy;
  }(PointProxy);

  /**
   * UTopo中的点的概念,
   * 封装zrender中的image,
   * 或者Circle生成一个固定大小的默认30*30大小的点
   *
   * @author zhangnt
   * @date 2019-3-5 17:00
   * @param pointConfig
   */

  var Point = /*#__PURE__*/function (_UTopoModel) {
    _inherits(Point, _UTopoModel);

    var _super = _createSuper(Point);

    function Point(pointConfig) {
      var _this;

      _classCallCheck(this, Point);

      _this = _super.call(this, pointConfig);
      _this.zlevel = GlobalConfig.level.POINT_LEVEL; //当前点是否选中
      if(pointConfig.devType === 'PLAT') {
        _this.zlevel = GlobalConfig.level.GROUP_LEVEL - 1
      }
      _this.selected = false;

      _this.init();

      return _this;
    } //初始化方法


    _createClass(Point, [{
      key: "init",
      value: function init() {
        if (!this.mtype) {
          if (this.style.image) {
            this.mtype = GlobalConfig.mtype.POINT_CORE_IMG;
          } else if (this.style.r) {
            this.mtype = GlobalConfig.mtype.POINT_CORE_CIR;
          } else {
            this.mtype = GlobalConfig.mtype.POINT_CORE_RECT;
          }
        }

        if (this.mtype === GlobalConfig.mtype.POINT_CORE_IMG) {
          this.pointProxy = new PointImgProxy();
        } else if (this.mtype === GlobalConfig.mtype.POINT_CORE_CIR || this.mtype === GlobalConfig.mtype.POINT_CORE_RECT) {
          this.pointProxy = new PointRectProxy();
        } else if (this.mtype === GlobalConfig.mtype.POINT_CORE_LINE) {
          this.pointProxy = new PointLineProxy();
        } //创建点生成代理对象

      }
      /**
       * 生出view的对象
       */

    }, {
      key: "initView",
      value: function initView(_storage) {
        this._storage = _storage;

        if (_storage._config.draggable === false) {
          this.draggable = false;
        }

        this.viewgroup = new Group();
        this.createPointView();
        this.createPointSignView();
        this.createPointSymbolView();
        this.addSimpleFlickerAnimation();
        this.addDragEvent();
        this.addMouseEvent();
      }
      /**
       * 创建点图形对象
       * @returns {*}
       */

    }, {
      key: "createPointView",
      value: function createPointView() {
        //有配置图片表示创建zr图形对象
        this.view = this.pointProxy.createView(this); //加入组

        this.viewgroup.add(this.view); //绑定model对象为this

        this.view.model = this;
        this.viewgroup.model = this;
      }
      /**
       * 创建文字标记
       */

    }, {
      key: "createPointSignView",
      value: function createPointSignView() {
        var pointGroup = this.viewgroup; //标记功能,线中心增加圆球配置显示短文字,数字

        if (this.sign) {
          //扩展为支持数组配置
          if (!(this.sign instanceof Array)) {
            this.sign = [this.sign];
          } //用line  text实现


          var signLabel = this.pointProxy.createPointSign(this);
          signLabel.forEach(function (sigh) {
            pointGroup.add(sigh);
          });
        }
      }
      /**
       * 创建图形标记
       */

    }, {
      key: "createPointSymbolView",
      value: function createPointSymbolView() {
        var _this2 = this;

        if (this.symbol && this.symbol instanceof Array && this.symbol.length > 0) {
          var symbol = this.pointProxy.createSymbolView(this);
          symbol.forEach(function (el) {
            _this2.viewgroup.add(el);
          });
        }
      }
      /**
       * 添加鼠标事件
       * 鼠标滑过
       * 鼠标点击
       * 鼠标移除
       */

    }, {
      key: "addMouseEvent",
      value: function addMouseEvent() {
        var _this3 = this;

        //silent表示,没有鼠标事件响应
        if (this.silent || this.pointProxy instanceof PointLineProxy) {
          return;
        } //点击选中事件


        this.view.on('click', function (e) {
          //按住ctrl点击高亮选中
          if (e.event.ctrlKey && _this3.selected === false) {
            _this3.select();
          } else {
            _this3.select(false);
          }
        });
        this.addPointHighLightEvent();
      }
      /**
       * 增加鼠标高亮事件
       * 分为 
       *      高亮圆圈
       *      高亮切图
       */

    }, {
      key: "addPointHighLightEvent",
      value: function addPointHighLightEvent() {
        var _this4 = this;

        //创建高亮效果shape
        this.highLightView = this.pointProxy.createHighLightView(this, this.view);
        this.view.on('mouseover', function (e) {
          if (e.event.button === 1 || _this4.selected || _this4._storage._view._focus) {
            return;
          }

          _this4.highlight(1);
        }); //鼠标移除数据

        this.view.on('mouseout', function (e) {
          if (e.event.button === 1 || _this4.selected || _this4._storage._view._focus) {
            return;
          }

          _this4.highlight(0);
        });
      }
      /**
       * 点高亮事件功能
       * @param {} flag 
       */

    }, {
      key: "highlight",
      value: function highlight(flag) {
        //如果highLightView 为空 表明是  高亮效果是切换图片
        if (this.highLightView == null) {
          this.pointProxy.changeViewImg(this.view, this, flag);
        } else {
          //否则是显示高亮圆圈
          if (flag === 1) {
            if (!this.selected) {
              this.highLightView.attr({
                z: 100000,
                invisible: false,
                style: {
                  stroke: this.hstyle.stroke ? this.hstyle.stroke : PointConfig.emphasis.stroke
                }
              });
            }
          } else {
            //高亮不可见
            this.highLightView.attr({
              z: 0,
              invisible: true
            });
          }
        }
      }
      /**
       * 给点添加拖拽事件
       */

    }, {
      key: "addDragEvent",
      value: function addDragEvent() {
        var _this5 = this;

        //修改对应点的drift方法,修改xy值不修改position
        //获取当前点相关联的所有线
        this.view.drift = function (dx, dy) {
          _this5.updateView(dx, dy);
        };
      }
      /**
       * 添加外圈涟漪效果
       */

    }, {
      key: "addSimpleFlickerAnimation",
      value: function addSimpleFlickerAnimation() {
        if (this.flicker.show) {
          var circle = this.pointProxy.createSimpleFlickerAnimation(this);

          if (circle != null) {
            this.viewgroup.add(circle);
          }
        }
      }
      /**
       * 移动到某位置
       */

    }, {
      key: "moveTo",
      value: function moveTo(x, y) {
        var center = this.getCenterPoint();
        var dx = x - center.x;
        var dy = y - center.y;
        this.updateView(dx, dy);
      }
    }, {
      key: "clearZrAnimationLayer",
      value: function clearZrAnimationLayer() {
        var view = this._storage._topo._view;
        view.clearZrAnimationLayer();
      }
      /**
       * 更新view层
       * @param dx 偏移量x
       * @param dy 偏移量y
       */

    }, {
      key: "updateView",
      value: function updateView(dx, dy) {
        var _this6 = this;

        if (this.viewgroup != null) {
          this.updateThisView();
          this.viewgroup.children().forEach(function (el) {
            //点坐标的变化,(后续加入状态图标的变化)
            _this6.pointProxy.updateEl(el, dx, dy);
          }); //更新线

          this.updateRelatedLinksView(); //更新父亲组大小

          this.updateRelatedGroupView(); //更新动画层显示效果

          this.clearZrAnimationLayer();
        }
      }
      /**
       * 更新
       */

    }, {
      key: "updateThisView",
      value: function updateThisView() {
        //如果是线形点
        if (this.pointProxy instanceof PointLineProxy) {
          var _storage = this._storage;

          var allLinks = _storage.getLinksOfPointById(this.id);

          var allPoints = new Set(); //获取该点关联的所有线

          allLinks.forEach(function (link) {
            allPoints.add(link.fromid);
            allPoints.add(link.toid);
          }); //循环所有点计算坐标

          var x1 = 1000000;
          var x2 = -1000000;
          allPoints.forEach(function (id) {
            var point = _storage.getPoint(id);

            var center = point.getCenterPoint();

            if (center.x < x1) {
              x1 = center.x;
            }

            if (center.x > x2) {
              x2 = center.x;
            }
          });
          this.pointProxy.updateElSize(this.view, x1, x2);
        } else {
          //判断对端是否为线点,如果对端为线点则更新对端
          var _storage2 = this._storage;

          var _allLinks = _storage2.getLinksOfPointById(this.id);

          var _allPoints = new Set(); //获取该点关联的所有线


          _allLinks.forEach(function (link) {
            _allPoints.add(link.fromid);

            _allPoints.add(link.toid);
          });

          _allPoints.forEach(function (id) {
            var point = _storage2.getPoint(id);

            if (point.pointProxy instanceof PointLineProxy) {
              point.updateThisView();
            }
          });
        }
      }
      /**
       * 更新关联父亲元素的视图大小
       */

    }, {
      key: "updateRelatedGroupView",
      value: function updateRelatedGroupView() {
        var _this7 = this;

        if (this.pid != null) {
          var pids = this.pid;

          if (!(pids instanceof Array)) {
            pids = [pids];
          }

          pids.forEach(function (pid) {
            var group = _this7._storage.getPoint(pid);

            if (group != null) {
              group.updateView();
            }
          });
        }
      }
      /**
       * 更新关联线的视图
       */

    }, {
      key: "updateRelatedLinksView",
      value: function updateRelatedLinksView() {
        var _storage = this._storage;

        var allLinks = _storage.getLinksOfPointById(this.id); //获取该点关联的所有线


        allLinks.forEach(function (link) {
          link.updateView();
        });
      }
      /**
       * 获取当前点的中心坐标
       */

    }, {
      key: "getCenterPoint",
      value: function getCenterPoint() {
        var scale = this.view.scale;
        var rect = this.view.getBoundingRect();
        return {
          x: (rect.x + rect.width / 2) * scale[0],
          y: (rect.y + rect.height / 2) * scale[1]
        };
      }
      /**
       * 获取点对象的包围盒大小
       */

    }, {
      key: "getInRect",
      value: function getInRect() {
        return this.view.getBoundingRect();
      }
      /**
       * 选中点,不传递flag 为选中,flag = false 为清空选中.
       * 选中之后增加动画效果等
       */

    }, {
      key: "select",
      value: function select(flag) {
        var id = this.id;
        var zlevel = this.view.zlevel; //jsSet对象

        var selectedPoint = this._storage._selectPoint;

        if (flag === undefined) {
          flag = true;
        } // let r0 = this.highLightView.shape.r0;
        // let r = this.highLightView.shape.r;


        if (flag) {
          if (!selectedPoint.has(id)) {
            selectedPoint.add(id);
            this.selected = true; //设置选中圆的zlevel大于点层级,保证显示

            this.highLightView.attr({
              zlevel: zlevel + 1,
              invisible: false,
              style: {
                stroke: PointConfig.select.color
              }
            }); // if(this.highLightView.animators.length>0){
            //     this.highLightView.animators[0].start();
            // }else {
            //     this.highLightView.animate('shape', true).delay(0).when(1000, {
            //         r: r + 5,
            //         r0: r0 + 5
            //     }).start();//开启动画
            // }
          }
        } else {
          //删除
          var delFlag = selectedPoint["delete"](id);
          this.selected = false;

          if (delFlag) {
            //还原高亮对象层级为小于点层级
            this.highLightView.attr({
              invisible: true,
              zlevel: zlevel - 1
            }); //this.highLightView.animators[0].stop();
          }
        }
      }
    }]);

    return Point;
  }(UTopoModel);

  var _arrayFill = function fill(value /* , start = 0, end = @length */) {
    var O = _toObject(this);
    var length = _toLength(O.length);
    var aLen = arguments.length;
    var index = _toAbsoluteIndex(aLen > 1 ? arguments[1] : undefined, length);
    var end = aLen > 2 ? arguments[2] : undefined;
    var endPos = end === undefined ? length : _toAbsoluteIndex(end, length);
    while (endPos > index) O[index++] = value;
    return O;
  };

  // 22.1.3.6 Array.prototype.fill(value, start = 0, end = this.length)


  _export(_export.P, 'Array', { fill: _arrayFill });

  _addToUnscopables('fill');

  /**
   * 贝塞尔曲线
   * @module zrender/shape/BezierCurve
   */

  var out = [];

  function someVectorAt(shape, t, isTangent) {
      var cpx2 = shape.cpx2;
      var cpy2 = shape.cpy2;
      if (cpx2 === null || cpy2 === null) {
          return [
              (isTangent ? cubicDerivativeAt : cubicAt)(shape.x1, shape.cpx1, shape.cpx2, shape.x2, t),
              (isTangent ? cubicDerivativeAt : cubicAt)(shape.y1, shape.cpy1, shape.cpy2, shape.y2, t)
          ];
      }
      else {
          return [
              (isTangent ? quadraticDerivativeAt : quadraticAt)(shape.x1, shape.cpx1, shape.x2, t),
              (isTangent ? quadraticDerivativeAt : quadraticAt)(shape.y1, shape.cpy1, shape.y2, t)
          ];
      }
  }

  var ZrBezierCurve = Path.extend({

      type: 'bezier-curve',

      shape: {
          x1: 0,
          y1: 0,
          x2: 0,
          y2: 0,
          cpx1: 0,
          cpy1: 0,
          // cpx2: 0,
          // cpy2: 0

          // Curve show percent, for animating
          percent: 1
      },

      style: {
          stroke: '#000',
          fill: null
      },

      buildPath: function (ctx, shape) {
          var x1 = shape.x1;
          var y1 = shape.y1;
          var x2 = shape.x2;
          var y2 = shape.y2;
          var cpx1 = shape.cpx1;
          var cpy1 = shape.cpy1;
          var cpx2 = shape.cpx2;
          var cpy2 = shape.cpy2;
          var percent = shape.percent;
          if (percent === 0) {
              return;
          }

          ctx.moveTo(x1, y1);

          if (cpx2 == null || cpy2 == null) {
              if (percent < 1) {
                  quadraticSubdivide(
                      x1, cpx1, x2, percent, out
                  );
                  cpx1 = out[1];
                  x2 = out[2];
                  quadraticSubdivide(
                      y1, cpy1, y2, percent, out
                  );
                  cpy1 = out[1];
                  y2 = out[2];
              }

              ctx.quadraticCurveTo(
                  cpx1, cpy1,
                  x2, y2
              );
          }
          else {
              if (percent < 1) {
                  cubicSubdivide(
                      x1, cpx1, cpx2, x2, percent, out
                  );
                  cpx1 = out[1];
                  cpx2 = out[2];
                  x2 = out[3];
                  cubicSubdivide(
                      y1, cpy1, cpy2, y2, percent, out
                  );
                  cpy1 = out[1];
                  cpy2 = out[2];
                  y2 = out[3];
              }
              ctx.bezierCurveTo(
                  cpx1, cpy1,
                  cpx2, cpy2,
                  x2, y2
              );
          }
      },

      /**
       * Get point at percent
       * @param  {number} t
       * @return {Array.<number>}
       */
      pointAt: function (t) {
          return someVectorAt(this.shape, t, false);
      },

      /**
       * Get tangent at percent
       * @param  {number} t
       * @return {Array.<number>}
       */
      tangentAt: function (t) {
          var p = someVectorAt(this.shape, t, true);
          return normalize(p, p);
      }
  });

  var LinkConfig = {
    "default": {
      color: '#ccc',
      lineWidth: 1,
      fontSize: 12,
      fontFamily: 'Microsoft YaHei',
      textFill: '#000',
      textPosition: 'inside'
    },
    //坐标便宜
    offset: {
      x1: 0,
      y1: 0,
      x2: 0,
      y2: 0
    },
    emphasis: {
      //线高亮宽度
      lineWidth: 3
    },
    //动画对象配置，以后可能要加入图片标签等
    animation: {
      //动画小球配置
      circle: {
        //颜色
        color: '#ed687e',
        loop: true,
        delay: 100,
        time: 5000
      },
      //路径动画配置
      path: {
        color: '#1aed5c',
        time: 5000
      }
    },
    sign: {
      //标记配置
      text: null,
      //文字默认大小12
      fontSize: 12,
      //字体颜色
      color: '#000',
      //背景颜色
      backgroundColor: null,
      //内边距
      padding: [2, 5],
      //文字行高
      textLineHeight: 16
    }
  };

  /**
   * 圆弧
   * @module zrender/graphic/shape/Arc
   */
  var ZrArrow = Path.extend({
    type: 'arrow',
    shape: {
      x: 0,
      y: 0,
      a: 0,
      r: 10
    },
    buildPath: function buildPath(ctx, shape) {
      var x = shape.x;
      var y = shape.y;
      var angle = shape.a;
      var p0 = {
        x: x,
        y: y
      };
      var r = shape.r; //返回三个点的坐标(箭头的三个点)

      var p1 = {
        x: x - r * Math.cos(Math.PI / 180 * (angle - 30)),
        y: y - r * Math.sin(Math.PI / 180 * (angle - 30))
      };
      var p2 = {
        x: x - r * Math.cos(Math.PI / 180 * (angle + 30)),
        y: y - r * Math.sin(Math.PI / 180 * (angle + 30))
      };
      var p3 = {
        x: x - r / 2 * Math.cos(Math.PI / 180 * angle),
        y: y - r / 2 * Math.sin(Math.PI / 180 * angle)
      };
      ctx.moveTo(p1.x, p1.y);
      ctx.lineTo(p0.x, p0.y);
      ctx.lineTo(p2.x, p2.y);
      ctx.lineTo(p3.x, p3.y);
      ctx.lineTo(p1.x, p1.y);
      ctx.closePath();
    }
  });

  /**
   * 反转贝塞尔曲线坐标
   * @param shape
   * @returns {{x1: *, y1: *, x2: *, y2: *, cpx1: number, cpy1: number}}
   */

  function reverseBezierCurveShape(shape) {
    var x1 = shape.x1,
        y1 = shape.y1,
        x2 = shape.x2,
        y2 = shape.y2,
        cpx1 = shape.cpx1,
        cpy1 = shape.cpy1;
    var shapeTmp = {
      x1: x2,
      y1: y2,
      x2: x1,
      y2: y1,
      cpx1: cpx1,
      cpy1: cpy1
    }; //处理shape的其他属性

    merge(shapeTmp, shape);
    return shapeTmp;
  }
  /**
   * 获取运动中的贝塞尔曲线点的位置
   */

  function getBezierCurvePointAt(shape, t) {
    if (!t) {
      t = 0;
    }

    var x = (1 - t) * (1 - t) * shape.x1 + 2 * t * (1 - t) * shape.cpx1 + t * t * shape.x2;
    var y = (1 - t) * (1 - t) * shape.y1 + 2 * t * (1 - t) * shape.cpy1 + t * t * shape.y2;
    return {
      x: x,
      y: y
    };
  }
  /**
   * 获取二阶贝塞尔曲线在指定t位置的斜率
   * 依据公式推导出以下结果
   */

  function getBezierCurveLinkRotation(lineShapeOption, t) {
    var x = (1 - t) * (lineShapeOption.cpx1 - lineShapeOption.x1) + t * (lineShapeOption.x2 - lineShapeOption.cpx1);
    var y = (1 - t) * (lineShapeOption.cpy1 - lineShapeOption.y1) + t * (lineShapeOption.y2 - lineShapeOption.cpy1);
    return -Math.atan2(y, x);
  }
  /**
   * 计算二阶贝塞尔曲线角度
   *
   */

  function getBezierCurveLinkAngle(lineShapeOption, t, reverse) {
    if (reverse) {
      var x = (1 - t) * (lineShapeOption.cpx1 - lineShapeOption.x2) + t * (lineShapeOption.x1 - lineShapeOption.cpx1);
      var y = (1 - t) * (lineShapeOption.cpy1 - lineShapeOption.y2) + t * (lineShapeOption.y1 - lineShapeOption.cpy1);
      return parseInt(Math.atan2(y, x) / Math.PI * 180);
    } else {
      var _x = (1 - t) * (lineShapeOption.cpx1 - lineShapeOption.x1) + t * (lineShapeOption.x2 - lineShapeOption.cpx1);

      var _y = (1 - t) * (lineShapeOption.cpy1 - lineShapeOption.y1) + t * (lineShapeOption.y2 - lineShapeOption.cpy1);

      return parseInt(Math.atan2(_y, _x) / Math.PI * 180);
    }
  }

  /**
   * 圆弧
   * @module zrender/graphic/shape/Arc
   */
  var Wave = Path.extend({
    type: 'wave',
    shape: {
      x1: 0,
      y1: 0,
      x2: 0,
      y2: 0,
      c: 0.5,
      l: 10
    },
    style: {
      fill: null
    },
    buildPath: function buildPath(ctx, shape) {
      if (shape.smooth == 'line') {
        shape.c = 0.4;
        this.buildPathLine(ctx, shape);
      } else {
        this.buildPathBezier(ctx, shape);
      }
    },
    buildPathLine: function buildPathLine(ctx, shape) {
      var x1 = shape.x1;
      var y1 = shape.y1;
      var x2 = shape.x2;
      var y2 = shape.y2;
      var c = shape.c; //计算长度

      var length = Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); //大致计算绘制多少个波浪

      var total = Math.floor(length / shape.l); //开始点

      var ox1 = x1;
      var oy1 = y1; //循环绘制

      ctx.moveTo(ox1, oy1);

      for (var i = 0; i <= total; i++) {
        //结束点
        var ox2 = x1 + (x2 - x1) / total * i;
        var oy2 = y1 + (y2 - y1) / total * i; //曲率

        var curveness = i % 2 == 0 ? c : -c; //波浪控制点

        var cpx1 = (ox1 + ox2) / 2 - (oy1 - oy2) * curveness;
        var cpy1 = (oy1 + oy2) / 2 - (ox2 - ox1) * curveness;
        ctx.lineTo(cpx1, cpy1);
        ctx.lineTo(ox2, oy2); //修改当前点

        ox1 = ox2;
        oy1 = oy2;
      }
    },
    buildPathBezier: function buildPathBezier(ctx, shape) {
      var x1 = shape.x1;
      var y1 = shape.y1;
      var x2 = shape.x2;
      var y2 = shape.y2;
      var c = shape.c; //计算长度

      var length = Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); //大致计算绘制多少个波浪

      var total = Math.floor(length / shape.l); //开始点

      var ox1 = x1;
      var oy1 = y1; //循环绘制

      ctx.moveTo(ox1, oy1);

      for (var i = 0; i <= total; i++) {
        //结束点
        var ox2 = x1 + (x2 - x1) / total * i;
        var oy2 = y1 + (y2 - y1) / total * i; //曲率

        var curveness = i % 2 == 0 ? c : -c; //波浪控制点

        var cpx1 = (ox1 + ox2) / 2 - (oy1 - oy2) * curveness;
        var cpy1 = (oy1 + oy2) / 2 - (ox2 - ox1) * curveness;
        ctx.quadraticCurveTo(cpx1, cpy1, ox2, oy2); //修改当前点

        ox1 = ox2;
        oy1 = oy2;
      }
    }
  });

  var LinkProxy = /*#__PURE__*/function () {
    function LinkProxy() {
      _classCallCheck(this, LinkProxy);
    }
    /**
     * 创建线对象
     */


    _createClass(LinkProxy, [{
      key: "createView",
      value: function createView(model) {
        //创建线对象的起始shape对象,记录在线上是为了提高效率
        updateLinkBezierCurveShape(model);

        if (model.mtype === GlobalConfig.mtype.LINK_CORE_TCL) {
          return UBezierLinkUtil.createTwoBezierCurve(model);
        } else if (model.mtype === GlobalConfig.mtype.LINK_CORE_OCL) {
          return UBezierLinkUtil.createBezierCurve(model);
        } else if (model.mtype === 'wave') {
          return UBezierLinkUtil.createWaveLine(model);
        }
      }
      /**
       * link组内的图形坐标位置变动修改
       * @param el
       * @param lineShapeOption
       */

    }, {
      key: "updateEl",
      value: function updateEl(el, lineShapeOption) {
        //线对象,动画线对象
        if (el instanceof ZrBezierCurve) {
          el.attr("shape", lineShapeOption);

          if (el.reverse) {
            var lineShapeOption2 = reverseBezierCurveShape(lineShapeOption);
            el.attr("shape", lineShapeOption2);
          }
        }

        if (el instanceof Wave) {
          el.attr("shape", lineShapeOption);
        } //如果是图片则代表为symbol对象


        if (el instanceof ZImage) {
          var position = getBezierCurvePointAt(lineShapeOption, el.t);
          var width = el.style.width;
          var height = el.style.height;
          var x = position.x - width / 2;
          var y = position.y - height / 2;
          el.attr("style", {
            x: x,
            y: y
          });
        } //如果是图片则代表为symbol对象


        if (el instanceof ZrCircle) {
          if (el.t) {
            var _position = getBezierCurvePointAt(lineShapeOption, el.t);

            var _x = _position.x;
            var _y = _position.y;
            el.attr("shape", {
              cx: _x,
              cy: _y
            });
          }
        } //如果是图片则代表为symbol对象


        if (el instanceof ZrArrow) {
          var _position2 = getBezierCurvePointAt(lineShapeOption, el.t);

          var rotation = getBezierCurveLinkAngle(lineShapeOption, el.t, el.reverse);
          var _x2 = _position2.x;
          var _y2 = _position2.y;
          el.attr("shape", {
            x: _x2,
            y: _y2,
            a: rotation
          });
        } //如果是文字则代表为sign配置对象
        //sign对象的修改要考虑旋转,仅仅在不缩放的时候时候对着的暂时


        if (el instanceof Text) {
          var _position3 = getBezierCurvePointAt(lineShapeOption, el.sign.t);

          var _x3 = _position3.x;
          var _y3 = _position3.y;
          var rect = el.getBoundingRect();
          var textAlign = el.sign.align;
          var textPosition = el.sign.position;

          if (textPosition === 'bottom') ; else if (textPosition === 'top') {
            _y3 = _y3 - rect.height;
          } else if (textPosition === 'inside') {
            _y3 = _y3 - rect.height / 2;
          }

          if (textAlign === 'left') ; else if (textAlign === 'right') {
            _x3 = _x3 - rect.width;
          } else if (textAlign === 'center') {
            _x3 = _x3 - rect.width / 2;
          }

          el.attr('style', {
            x: _x3,
            y: _y3
          });
          var rotate = el.sign.rotate;

          if (rotate) {
            //以下计算均只能在没有缩放的情况下正确
            //计算文字旋转中心
            var center = [_x3 + rect.width / 2, _y3 + rect.height / 2]; //计算文字旋转角度

            var _rotation = getBezierCurveLinkRotation(lineShapeOption, el.sign.t);

            el.attr('origin', center);
            el.attr('rotation', _rotation);
          }
        }
      }
      /**
       * 创建标记图形
       */

    }, {
      key: "createSymbolView",
      value: function createSymbolView(model) {
        var lineShape = clone(model.linkShapeOption);
        var symbolViews = [];
        model.symbol.forEach(function (s) {
          try {
            var t = s.hasOwnProperty('t') ? s.t : 0.5; //获取标记生成位置

            var pat = getBezierCurvePointAt(lineShape, t);
            var symbol = null;

            if (s.image) {
              //绘制图片
              var width = s.width ? s.width : 16;
              var height = s.height ? s.height : 16;
              var x = pat.x - width / 2;
              var y = pat.y - height / 2; //生成图标

              var imageOption = {
                zlevel: model.zlevel + 1,
                t: t,
                style: {
                  x: x,
                  y: y,
                  width: width,
                  height: height,
                  image: s.image
                },
                draggable: false
              };
              symbol = new ZImage(imageOption);
            } else if (s.type === 'arrow') {
              //绘制箭头
              var rotation = getBezierCurveLinkAngle(lineShape, t, s.reverse);
              var _x4 = pat.x;
              var _y4 = pat.y;
              var arrowOption = {
                zlevel: model.zlevel + 1,
                t: t,
                reverse: s.reverse,
                shape: {
                  x: _x4,
                  y: _y4,
                  a: rotation,
                  r: s.r ? s.r : 10
                },
                style: {
                  fill: s.stroke === undefined ? 'green' : s.stroke,
                  stroke: s.stroke === undefined ? 'green' : s.stroke,
                  lineWidth: 2
                },
                draggable: false
              };
              symbol = new ZrArrow(arrowOption);
            } else {
              //绘制小球
              var _x5 = pat.x;
              var _y5 = pat.y;
              var r = 4; //生成图标

              var circleOption = {
                zlevel: model.zlevel + 1,
                t: t,
                shape: {
                  cx: _x5,
                  cy: _y5,
                  r: r
                },
                style: {
                  fill: s.fill,
                  stroke: s.stroke === undefined ? 'red' : s.stroke,
                  lineWidth: 2
                },
                draggable: false
              };
              symbol = new ZrCircle(circleOption);
            }

            symbol.on("click", function () {
              s.onclick(symbol);
            });
            symbol.model = s;
            symbolViews.push(symbol);
          } catch (e) {
            console.warn(e);
          }
        });
        return symbolViews;
      }
      /**
       * 获取线对应的两点坐标以及生成的贝塞尔曲线数据
       * @param model
       */

    }, {
      key: "updateLinkBezierCurveShape",
      value: function updateLinkBezierCurveShape(model) {
        var fromid = model.fromid;
        var toid = model.toid; //曲度

        var curveness = model.style.curveness ? model.style.curveness : 0; //起始终止节点

        var fromPoint = model._storage.getPoint(fromid);

        var toPoint = model._storage.getPoint(toid); //获取当前缩放值


        var scale = fromPoint.view.scale;
        var fromCenter = fromPoint.getCenterPoint();
        var toCenter = toPoint.getCenterPoint();
        var x1 = fromCenter.x / scale[0],
            y1 = fromCenter.y / scale[1],
            x2 = toCenter.x / scale[0],
            y2 = toCenter.y / scale[1];
        var p = null;

        if (fromPoint.mtype === 'line') {
          x1 = x2;
          p = 'f';
        }

        if (toPoint.mtype === 'line') {
          x2 = x1;
          p = 't';
        }

        var cpx1 = (x1 + x2) / 2 - (y1 - y2) * curveness;
        var cpy1 = (y1 + y2) / 2 - (x2 - x1) * curveness;
        model.linkShapeOption = {
          x1: x1,
          y1: y1,
          x2: x2,
          y2: y2,
          cpx1: cpx1,
          cpy1: cpy1,
          p: p
        };
      }
      /**
       * 创建文字标记：利用绘制一条同样位置的看不见的线的text配置生成文字效果
       */

    }, {
      key: "createLinkSign",
      value: function createLinkSign(model) {
        var lineShapeOption = clone(model.linkShapeOption);
        var signViews = [];
        var signs = model.sign;
        signs.forEach(function (sign) {
          //直接在制定位置写文字
          var lineOption = {
            zlevel: model.zlevel,
            z: 10000,
            //记录配置对象
            sign: {
              t: sign.t ? sign.t : 0.5,
              align: sign.align ? sign.align : 'center',
              position: sign.position ? sign.position : 'inside',
              rotate: sign.rotate ? sign.rotate : false
            },
            style: {
              x: lineShapeOption.x,
              y: lineShapeOption.y,
              text: sign.text,
              opacity: 1,
              stroke: null,
              lineWidth: 0,
              textBorderWidth: 0,
              textBorderRadius: 10,
              textWidth: 12,
              fontSize: sign.fontSize ? sign.fontSize : LinkConfig.sign.fontSize,
              fontFamily: model.style.fontFamily ? model.style.fontFamily : LinkConfig["default"].fontFamily,
              textLineHeight: sign.textLineHeight ? sign.textLineHeight : LinkConfig.sign.textLineHeight,
              textFill: sign.color ? sign.color : LinkConfig.sign.color,
              textBackgroundColor: sign.backgroundColor ? sign.backgroundColor : LinkConfig.sign.backgroundColor,
              textPadding: sign.padding ? sign.padding : LinkConfig.sign.padding
            },
            draggable: false
          };
          signViews.push(new Text(lineOption));
        });
        return signViews;
      }
    }]);

    return LinkProxy;
  }();
  /**
   * 根据配置生成点的视图对象
   * image or circle
   * @param model
   */

  var UBezierLinkUtil = {
    //创建两种颜色的曲线
    createTwoBezierCurve: function createTwoBezierCurve(model) {
      //模型上获取起点终点坐标
      var lineShapeOption = clone(model.linkShapeOption); //默认配置

      var lineOption1 = getDefaultLineOption(model); //显示0.5

      lineShapeOption.percent = 0.5;
      var option1 = {
        shape: lineShapeOption,
        style: {
          stroke: model.style.acolor ? model.style.acolor : LinkConfig["default"].color
        }
      }; //合并配置

      merge(lineOption1, option1, true); //生成第一条线

      var line1 = new ZrBezierCurve(lineOption1); //创建第二条线

      var lineShapeOption2 = reverseBezierCurveShape(lineShapeOption);
      var option2 = {
        reverse: true,
        shape: lineShapeOption2,
        style: {
          text: '',
          stroke: model.style.bcolor ? model.style.bcolor : LinkConfig["default"].color
        }
      };
      var lineOption2 = clone(lineOption1);
      merge(lineOption2, option2, true);
      var line2 = new ZrBezierCurve(lineOption2);

      if (lineShapeOption.p) {
        this.createSingleEndedPoint(model, lineShapeOption.p);
      }

      return [line1, line2];
    },
    //创建单颜色的曲线
    createBezierCurve: function createBezierCurve(model) {
      //模型上获取起点终点坐标
      var lineShapeOption = clone(model.linkShapeOption);
      var lineOption = getDefaultLineOption(model);
      var option1 = {
        shape: lineShapeOption,
        style: {
          stroke: model.style.color ? model.style.color : LinkConfig["default"].color
        }
      };
      merge(lineOption, option1, true);
      var line1 = new ZrBezierCurve(lineOption);

      if (lineShapeOption.p) {
        this.createSingleEndedPoint(model, lineShapeOption.p);
      }

      return [line1];
    },
    createWaveLine: function createWaveLine(model) {
      var lineShapeOption = clone(model.linkShapeOption);
      var lineOption = getDefaultLineOption(model);
      var option1 = {
        shape: lineShapeOption,
        style: {
          stroke: model.style.color ? model.style.color : LinkConfig["default"].color
        }
      };
      merge(lineOption, option1, true);
      var line1 = new Wave(lineOption);
      return [line1];
    },
    //如果一端为线形点要创建端点
    createSingleEndedPoint: function createSingleEndedPoint(model, p) {
      model.symbol.push({
        t: p === 'f' ? 0.01 : 0.99,
        stroke: 'green',
        fill: '#fff'
      });
    }
  };
  /**
   * 线的基础配置对象
   * @param model
   */

  function getDefaultLineOption(model) {
    return {
      id: model.id.toString(),
      zlevel: model.zlevel,
      position: [0, 0],
      style: {
        lineDash: model.style.lineDash ? model.style.lineDash : null,
        text: model.name ? model.name : model.style.text ? model.style.text : '',
        fontSize: model.style.fontSize ? model.style.fontSize : LinkConfig["default"].fontSize,
        fontFamily: model.style.fontFamily ? model.style.fontFamily : LinkConfig["default"].fontFamily,
        lineWidth: model.style.lineWidth ? model.style.lineWidth : LinkConfig["default"].lineWidth,
        textPosition: model.style.textPosition ? model.style.textPosition : LinkConfig["default"].textPosition,
        textFill: model.style.textFill ? model.style.textFill : LinkConfig["default"].textFill,
        transformText: true
      },
      draggable: false
    };
  }
  /**
   * 根据model数据获取对应的点数据,从而获取更新对应新的线坐标位置的方法。
   * @param model
   */


  function updateLinkBezierCurveShape(model) {
    var fromid = model.fromid;
    var toid = model.toid; //曲度

    var curveness = model.style.curveness ? model.style.curveness : 0; //起始终止节点

    var fromPoint = model._storage.getPoint(fromid);

    var toPoint = model._storage.getPoint(toid); //获取当前缩放值


    var scale = fromPoint.view.scale;
    var fromCenter = fromPoint.getCenterPoint();
    var toCenter = toPoint.getCenterPoint();
    var x1 = fromCenter.x / scale[0] + fromPoint.view.position[0],
        y1 = fromCenter.y / scale[1] + fromPoint.view.position[1],
        x2 = toCenter.x / scale[0] + toPoint.view.position[0],
        y2 = toCenter.y / scale[1] + toPoint.view.position[1];
    var p = null;

    if (fromPoint.mtype === 'line') {
      x1 = x2;
      p = 'f';
    }

    if (toPoint.mtype === 'line') {
      x2 = x1;
      p = 't';
    }

    var cpx1 = (x1 + x2) / 2 - (y1 - y2) * curveness;
    var cpy1 = (y1 + y2) / 2 - (x2 - x1) * curveness;
    model.linkShapeOption = {
      x1: x1,
      y1: y1,
      x2: x2,
      y2: y2,
      cpx1: cpx1,
      cpy1: cpy1,
      p: p
    };
  }

  /**
   * 创建动画代理类
   * @constructor
   */

  var LinkAnimationProxy = /*#__PURE__*/function () {
    function LinkAnimationProxy() {
      _classCallCheck(this, LinkAnimationProxy);
    }
    /**
     *
     * 创建动画图形对象,贝塞尔曲线路径,路径执行过程中显示小球
     *
     * 返回[动画line,动画小球]
     */


    _createClass(LinkAnimationProxy, [{
      key: "createCircleAnimation",
      value: function createCircleAnimation(model) {
        //对线的起点开始到终点,precent 0 -> 1,创建起点小球
        //模型上获取起点终点坐标
        var lineShapeOption = clone(model.linkShapeOption);
        var lineOption = getDefaultLineOption$1(model);
        lineShapeOption.percent = 0;
        var option1 = {
          zlevel: model.zlevel - 1,
          invisible: true,
          //设置不可见
          shape: lineShapeOption,
          style: {
            lineWidth: 1
          }
        };
        merge(lineOption, option1, true);
        var link = new ZrBezierCurve(lineOption); //运动小球

        var point = new ZrCircle({
          amtype: 'link_point',
          zlevel: GlobalConfig.level.ANIMATION_LEVEL,
          shape: {
            cx: lineShapeOption.x1,
            cy: lineShapeOption.y1,
            r: model.animation.r ? model.animation.r : 5
          },
          style: {
            fill: model.animation.color ? model.animation.color : LinkConfig.animation.circle.color
          }
        }); //生成动画对象

        var loop = model.animation.hasOwnProperty("loop") ? model.animation.loop : LinkConfig.animation.circle.loop;
        var delay = model.animation.hasOwnProperty("delay") ? model.animation.delay : LinkConfig.animation.circle.delay;
        var time = model.animation.hasOwnProperty("time") ? model.animation.time : LinkConfig.animation.circle.time; //执行目标

        link.animate('shape', loop).delay(delay).when(time, {
          percent: 1
        }).during(function (shape) {
          //动画过程中处理关联小球位置
          var pat = getBezierCurvePointAt$1(shape, shape.percent);
          point.attr('shape', {
            cx: pat.x,
            cy: pat.y
          });
        }).done(function () {
          //如果动画结束则设置小球隐藏
          point.attr({
            invisible: true
          });
        }).start(); //开启动画

        return [link, point];
      }
      /**
       * 创建路径动画对象
       * @param model
       * @returns {*[]}
       */

    }, {
      key: "createPathCircleAnimation",
      value: function createPathCircleAnimation(model, config) {
        //对线的起点开始到终点,precent 0 -> 1,创建起点小球
        //模型上获取起点终点坐标
        var lineShapeOption = clone(model.linkShapeOption);

        if (config.reverse) {
          lineShapeOption = reverseBezierCurveShape$1(lineShapeOption);
        }

        var lineOption = getDefaultLineOption$1(model);
        lineShapeOption.percent = 0;
        var option1 = {
          reverse: config.reverse,
          zlevel: model.zlevel - 1,
          invisible: true,
          //设置不可见
          shape: lineShapeOption,
          style: {
            lineWidth: 1
          }
        };
        merge(lineOption, option1, true);
        var link = new ZrBezierCurve(lineOption);
        return [link];
      }
      /**
       * 创建线的闪烁效果
       */

    }, {
      key: "createSimpleFlickerAnimationModel",
      value: function createSimpleFlickerAnimationModel(models) {
        //如果添加了闪烁效果,则将Z值增大
        models.forEach(function (link) {
          var opacity = 0.5;
          link.attr('style', {
            opacity: opacity
          });
          link.attr('z', 10000); //获取一次变化的总时间

          var flickerTime = link.model.flicker.time ? link.model.flicker.time : 1000; //一个状态变化的时间

          var flickerTimeTmp = flickerTime / 4;
          var lineWidth = link.style.lineWidth;
          link.animate('style', true).delay(100).when(flickerTimeTmp, {
            opacity: opacity,
            lineWidth: lineWidth
          }).when(flickerTimeTmp * 2, {
            opacity: 1,
            lineWidth: lineWidth + 3
          }).when(flickerTimeTmp * 3, {
            opacity: 1,
            lineWidth: lineWidth + 3
          }).when(flickerTimeTmp * 4, {
            opacity: opacity,
            lineWidth: lineWidth
          }).start();
        });
      }
    }]);

    return LinkAnimationProxy;
  }();
  /**
   * 线的基础配置对象
   * @param model
   */

  function getDefaultLineOption$1(model) {
    return {
      id: model.id.toString(),
      zlevel: model.zlevel,
      position: [0, 0],
      style: {
        lineDash: model.style.lineDash ? model.style.lineDash : null,
        text: model.name ? model.name : model.style.text ? model.style.text : '',
        fontSize: model.style.fontSize ? model.style.fontSize : LinkConfig["default"].fontSize,
        fontFamily: model.style.fontFamily ? model.style.fontFamily : LinkConfig["default"].fontFamily,
        lineWidth: model.style.lineWidth ? model.style.lineWidth : LinkConfig["default"].lineWidth,
        textPosition: model.style.textPosition ? model.style.textPosition : LinkConfig["default"].textPosition,
        textFill: model.style.textFill ? model.style.textFill : LinkConfig["default"].textFill,
        transformText: true
      },
      draggable: false
    };
  }
  /**
   * 反转贝塞尔曲线坐标
   * @param shape
   * @returns {{x1: *, y1: *, x2: *, y2: *, cpx1: number, cpy1: number}}
   */


  function reverseBezierCurveShape$1(shape) {
    var x1 = shape.x1,
        y1 = shape.y1,
        x2 = shape.x2,
        y2 = shape.y2,
        cpx1 = shape.cpx1,
        cpy1 = shape.cpy1;
    var shapeTmp = {
      x1: x2,
      y1: y2,
      x2: x1,
      y2: y1,
      cpx1: cpx1,
      cpy1: cpy1
    };
    merge(shapeTmp, shape);
    return shapeTmp;
  }
  /**
   * 获取运动中的贝塞尔曲线点的位置
   */


  function getBezierCurvePointAt$1(shape, t) {
    if (!t) {
      t = 0;
    }

    var x = (1 - t) * (1 - t) * shape.x1 + 2 * t * (1 - t) * shape.cpx1 + t * t * shape.x2;
    var y = (1 - t) * (1 - t) * shape.y1 + 2 * t * (1 - t) * shape.cpy1 + t * t * shape.y2;
    return {
      x: x,
      y: y
    };
  }

  /**
   * 线的实现类
   *
   */

  var Link = /*#__PURE__*/function (_UTopoModel) {
    _inherits(Link, _UTopoModel);

    var _super = _createSuper(Link);

    function Link(linkConfig) {
      var _this;

      _classCallCheck(this, Link);

      _this = _super.call(this, linkConfig);
      _this.zlevel = GlobalConfig.level.LINK_LEVEl;
      /*数据层*/

      _this.fromid = linkConfig.fromid;
      _this.toid = linkConfig.toid; //线上的状态图标

      _this.stateImgs = []; //记录线的起点,终点,控制点信息,同步保存
      //初始化创建,更新的时候会改变

      _this.linkShapeOption = null;

      _this.init(linkConfig);

      return _this;
    } //初始化方法


    _createClass(Link, [{
      key: "init",
      value: function init(linkConfig) {
        //根据配置得知要创建的线是什么类型的线
        if (!this.mtype) {
          if (this.style.acolor && this.style.bcolor) {
            this.mtype = GlobalConfig.mtype.LINK_CORE_TCL;
          } else if (this.style.color) {
            this.mtype = GlobalConfig.mtype.LINK_CORE_OCL;
          } else {
            this.mtype = GlobalConfig.mtype.LINK_CORE_OCL;
          }
        }

        this.linkProxy = new LinkProxy();
        this.linkAnimationProxy = new LinkAnimationProxy();
      }
      /**
       * 生出view的对象
       */

    }, {
      key: "initView",
      value: function initView(_storage) {
        this._storage = _storage;
        this.draggable = _storage._config.draggable;
        this.viewgroup = new Group();
        this.createLinkView();
        this.createLinkSignView();
        this.createLinkSymbolView();
        this.addMouseEvent();
        this.addSimpleFlickerAnimation();
        this.addSimpleAnimation();
      }
      /**
       * 更新线的view视图层状态
       * 如果是单条线则直接改变shape
       * 如果是组,则循环里面的点进行修改
       */

    }, {
      key: "updateView",
      value: function updateView() {
        var _this2 = this;

        if (this.viewgroup != null) {
          //更新线坐标数据,第二个参数表示要记录updateview之前的线的状态是否是高亮
          this.highlight(0, true);
          this.linkProxy.updateLinkBezierCurveShape(this);
          this.viewgroup.children().forEach(function (el) {
            //线对象,动画线对象(触发动画小球对象跟着变动)
            //线对象,双色两端线,动画线,sign线
            _this2.linkProxy.updateEl(el, _this2.linkShapeOption);
          });
          this.highlight(this.highlightFlag);
        }
      }
      /**
       * 创建线对象
       * 实现原理,组管理线和线上状态
       *      管理两条线 a-b,b-a两条线,各画一半,两条线颜色分别为acolor,bcolor
       *      管理一条线 a-b
       * @returns {Group}
       */

    }, {
      key: "createLinkView",
      value: function createLinkView() {
        var _this3 = this;

        var lineGroup = this.viewgroup; //创建线
        //如果双颜色的曲线则lines为数组,单颜色线也为数组

        this.view = this.linkProxy.createView(this);
        this.view.forEach(function (line) {
          lineGroup.add(line);
        }); //为线对象绑定model对象

        this.view.forEach(function (link) {
          link.model = _this3;
        });
        this.viewgroup.model = this;
      }
      /**
       * 创建线上的标记视图
       */

    }, {
      key: "createLinkSymbolView",
      value: function createLinkSymbolView() {
        var _this4 = this;

        if (this.symbol && this.symbol instanceof Array && this.symbol.length > 0) {
          var symbol = this.linkProxy.createSymbolView(this);
          symbol.forEach(function (el) {
            _this4.viewgroup.add(el);
          });
        }
      }
      /**
       * 创建线上的标记功能,文字实现
       */

    }, {
      key: "createLinkSignView",
      value: function createLinkSignView() {
        var lineGroup = this.viewgroup; //标记功能,线中心增加圆球配置显示短文字,数字

        if (this.sign) {
          //扩展为支持数组配置
          if (!(this.sign instanceof Array)) {
            this.sign = [this.sign];
          } //用line  text实现


          var signLabel = this.linkProxy.createLinkSign(this);
          signLabel.forEach(function (sigh) {
            lineGroup.add(sigh);
          });
        }
      }
      /**
       * 为线对象增加鼠标事件
       * 鼠标 移入:
       *     移出:
       */

    }, {
      key: "addMouseEvent",
      value: function addMouseEvent() {
        var _this5 = this;

        //组添加事件
        this.viewgroup.on('mouseover', function (e) {
          //保证拖拽的过程中,如果移动到了线上,不触发高亮效果
          if (e.event.button === 1 || _this5._storage._view._focus) {
            return;
          }

          _this5.highlight(1);
        });
        this.viewgroup.on('mouseout', function (e) {
          if (e.event.button === 1 || _this5._storage._view._focus) {
            return;
          }

          _this5.highlight(0);
        });
      }
    }, {
      key: "highlight",
      value: function highlight(flag, isDrage) {
        if (!isDrage) {
          this.highlightFlag = flag;
        }

        var zr = this._storage._topo._view._zrender;

        if (flag === 1) {
          this.viewgroup.children().forEach(function (el) {
            zr.addHover(el, {
              lineWidth: 3
            });
          });
          zr.refresh();
        } else {
          this.viewgroup.children().forEach(function (el) {
            zr.removeHover(el);
          });
        }
      }
      /**
       * 增加线上的简单动画效果
       * 实现思路:利用贝塞尔曲线的percent属性,绘制一条看不见的线L。
       *  L实现动画,在动画实现过程中,处理小球C位置,用贝塞尔曲线方程式和percent属性
       *  在线更新的过程中,会更新到贝塞尔曲线动画对应的线L。
       *  L的改变由于动画还在改变,L的shepe属性变动导致小球C跟着变动
       */

    }, {
      key: "addSimpleAnimation",
      value: function addSimpleAnimation() {
        //表示开启了动画
        if (this.animation.show || this._storage._topo._config.animation) {
          //创建动画对象
          var animationModel = this.linkAnimationProxy.createCircleAnimation(this);
          this.viewgroup.add(animationModel[0]);
          this.viewgroup.add(animationModel[1]);
        }
      }
      /**
       * 线增加路径动过辅助功能
       */

    }, {
      key: "addPathSimpleAnimation",
      value: function addPathSimpleAnimation(opt) {
        //创建动画对象
        var animationModel = this.linkAnimationProxy.createPathCircleAnimation(this, opt);
        this.viewgroup.add(animationModel[0]);
        return animationModel[0];
      }
      /**
       * 增加线的闪烁功能
       * 利用线的透明度和宽度实现闪烁效果
       */

    }, {
      key: "addSimpleFlickerAnimation",
      value: function addSimpleFlickerAnimation() {
        //若有有闪烁
        if (this.flicker.show) {
          //添加线的闪烁效果，透明度加宽度闪烁
          this.linkAnimationProxy.createSimpleFlickerAnimationModel(this.view);
        }
      }
    }]);

    return Link;
  }(UTopoModel);

  /**
   * utopo数据存储类
   * @author zhangnt
   * @param utopo
   */

  var UTopoStorage = /*#__PURE__*/function () {
    function UTopoStorage(utopo) {
      _classCallCheck(this, UTopoStorage);

      //topo对象唯一
      this._topo = utopo; //LogicTopoView对象

      this._view = utopo._view; //配置对象

      this._config = utopo._config; //所有点

      this._allPoints = new Map();
      this._allPointIds = new Set(); //所有线

      this._allLinks = new Map(); //点线关系{p1: Set[l1,l2,l3]}

      this._pointLinkRelation = new Map(); //点的父子关系{p1: Set[l1,l2,l3]}

      this._parentChildRelation = new Map(); //当前选中的点

      this._selectPoint = new Set(); //调用初始化方法

      this.init();
    }
    /**
     * 初始化方法
     */


    _createClass(UTopoStorage, [{
      key: "init",
      value: function init() {}
      /**
       * 添加点的方法
       * @param points
       */

    }, {
      key: "addPoint",
      value: function addPoint(points) {
        var _this = this;

        if (!(points instanceof Array)) {
          points = [points];
        }

        var hasGroupPoint = false; //使用箭头函数

        points.forEach(function (pointTemp) {
          var id = pointTemp.id; //判断点是否存在,存在则跳出,继续下一次循环

          if (_this._allPoints.has(id)) {
            return;
          }

          if (pointTemp instanceof Group$1) {
            hasGroupPoint = true;
          } //记录拓扑信息


          _this._allPoints.set(id, pointTemp);

          _this._allPointIds.add(id); //记录父子关系


          if (pointTemp.pid) {
            var parentId = pointTemp.pid;

            if (!(parentId instanceof Array)) {
              parentId = [parentId];
            }

            parentId.forEach(function (pid) {
              var pointId = pointTemp.id;

              if (!_this._parentChildRelation.has(pid)) {
                _this._parentChildRelation.set(pid, new Set([pointId]));
              } else {
                _this._parentChildRelation.get(pid).add(pointId);
              }
            });
          } //初始化点对象的view视图


          pointTemp.initView(_this); //设置topo对象.

          if (pointTemp.viewgroup != null) {
            //调用的是logicTopoView中的addShape()
            _this._view.add(pointTemp.viewgroup);
          }
        });
        this.autoGroupLevel(hasGroupPoint);

        this._view.refresh();
      }
      /**
       * 自动计算组的层级
       */

    }, {
      key: "autoGroupLevel",
      value: function autoGroupLevel(hasGroupPoint) {
        //如果为真则计算层级更新
        if (hasGroupPoint) {
          this._allPoints.forEach(function (el) {
            if (el instanceof Group$1) {
              el.updateViewLevel();
            }
          });
        }
      }
      /**
       * 添加线的方法
       * @param links
       */

    }, {
      key: "addLink",
      value: function addLink(links) {
        var _this2 = this;

        //默认转数组进行计算
        if (!(links instanceof Array)) {
          links = [links];
        } //箭头函数，this就是this


        links.forEach(function (linkTemp) {
          var id = linkTemp.id; //按照线id进行判断是否加入过这条线,没有添加

          if (!_this2._allLinks.has(id)) {
            //维护线map
            var fromid = linkTemp.fromid;
            var toid = linkTemp.toid; //从所有的点中获取指定点

            var fromPoint = _this2.getPoint(fromid);

            var toPoint = _this2.getPoint(toid); //线的两端能找到点


            if (fromPoint != null && toPoint != null) {
              //加入全部线的集合
              _this2._allLinks.set(id, linkTemp); //维护点线信息


              var fromLinkRelation = _this2._pointLinkRelation.get(fromid);

              if (fromLinkRelation === undefined) {
                fromLinkRelation = new Set();

                _this2._pointLinkRelation.set(fromid, fromLinkRelation);
              }

              fromLinkRelation.add(linkTemp.id);

              var toLinkRelation = _this2._pointLinkRelation.get(toid);

              if (toLinkRelation === undefined) {
                toLinkRelation = new Set();

                _this2._pointLinkRelation.set(toid, toLinkRelation);
              }

              toLinkRelation.add(linkTemp.id); //生成视图数据,生成视图数据的实例，linkTemp中的view指向这个实例

              linkTemp.initView(_this2); //加入zrenderShape

              if (linkTemp.viewgroup != null) {
                //调用的是LogicTopoView的addShape方法
                //所以在zrender画图时，传的是linkTemp的view
                _this2._view.add(linkTemp.viewgroup);
              }
            } else {
              console.warn("线没有找到对应的双端点" + id + "#" + fromid + "---" + toid);
            }
          }
        });
        this.autoLinkCurveness();

        this._view.refresh();
      }
      /**
       * 自动计算线的曲率
       */

    }, {
      key: "autoLinkCurveness",
      value: function autoLinkCurveness() {
        var _this3 = this;

        var curvenessInteval = this._config.curvenessInteval != undefined ? this._config.curvenessInteval : 25;
        curvenessInteval = 100 / curvenessInteval;
        var keyArr = this._allPointIds;
        keyArr.forEach(function (fromid) {
          keyArr.forEach(function (toid) {
            var links = _this3.getLinksBetweenTwoPoint(fromid, toid);

            if (links.size > 1) {
              var index = 0;

              if (links.size % 2 === 0) {
                index = 1;
              }

              var _iterator = _createForOfIteratorHelper(links),
                  _step;

              try {
                for (_iterator.s(); !(_step = _iterator.n()).done;) {
                  var linkid = _step.value;

                  if (index++ === 0 && links.size % 2 === 1) {
                    var _linkTemp = _this3.getLink(linkid);

                    _linkTemp.style.curveness = 0;
                    continue;
                  }

                  var linkTemp = _this3.getLink(linkid);

                  var curveness = 0; //同向线，曲率为正，否则为负

                  if (linkTemp.fromid === fromid) {
                    if (index % 2 === 0) {
                      curveness = index / 2 / curvenessInteval;
                    } else {
                      curveness = -((index - 1) / 2) / curvenessInteval;
                    }
                  } else {
                    if (index % 2 === 0) {
                      curveness = -(index / 2) / curvenessInteval;
                    } else {
                      curveness = (index - 1) / 2 / curvenessInteval;
                    }
                  }

                  linkTemp.style.curveness = curveness;
                }
              } catch (err) {
                _iterator.e(err);
              } finally {
                _iterator.f();
              }
            }
          });
        });
      }
      /**
       * 删除点
       * @param {} pointid 
       */

    }, {
      key: "delPoint",
      value: function delPoint(pointids) {
        var _this4 = this;

        if (!(pointids instanceof Array)) {
          pointids = [pointids];
        } //循环所有点


        pointids.forEach(function (id) {
          var point = _this4._allPoints.get(id);

          if (point) {
            //删除点
            _this4._allPoints["delete"](id);

            _this4._allPointIds["delete"](id);

            _this4._view.remove(point.viewgroup);

            if (point instanceof Point) {
              //删除父子关系
              var pid = point.pid;

              if (_this4._parentChildRelation.has(pid)) {
                var parentChildRelation = _this4._parentChildRelation.get(pid);

                parentChildRelation["delete"](id);

                if (parentChildRelation.size == 0) {
                  _this4._parentChildRelation["delete"](pid);
                }
              } //删除点线关系


              if (_this4._pointLinkRelation.has(id)) {
                var linkids = _this4._pointLinkRelation.get(id);

                _this4._pointLinkRelation["delete"](id); //删除相关联线


                var linkidArr = [];

                var _iterator2 = _createForOfIteratorHelper(linkids),
                    _step2;

                try {
                  for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
                    var linkid = _step2.value;
                    linkidArr.push(linkid);
                  }
                } catch (err) {
                  _iterator2.e(err);
                } finally {
                  _iterator2.f();
                }

                _this4.delLink(linkidArr);
              }
            } else if (point instanceof Group$1) {
              var _pid = point.pid; //如果组对应还有父亲,则删除该对象对应的父子关系数据

              if (_this4._parentChildRelation.has(_pid)) {
                var _parentChildRelation = _this4._parentChildRelation.get(_pid);

                _parentChildRelation["delete"](id); //如果对应父亲没有了孩子则,清除该父子关系对象


                if (_parentChildRelation.size == 0) {
                  _this4._parentChildRelation["delete"](_pid);
                }
              } //删除组下的所有点数据


              var _pointids = _this4._parentChildRelation.get(id);

              var pointidArr = [];

              var _iterator3 = _createForOfIteratorHelper(_pointids),
                  _step3;

              try {
                for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
                  var pointid = _step3.value;
                  pointidArr.push(pointid);
                }
              } catch (err) {
                _iterator3.e(err);
              } finally {
                _iterator3.f();
              }

              _this4.delPoint(pointidArr);
            }
          }
        });

        this._view.refresh();
      }
      /**
       * 删除线
       * @param {} linkid 
       */

    }, {
      key: "delLink",
      value: function delLink(linkids) {
        var _this5 = this;

        if (!(linkids instanceof Array)) {
          linkids = [linkids];
        }

        linkids.forEach(function (id) {
          var link = _this5._allLinks.get(id);

          if (link) {
            //删除线
            _this5._allLinks["delete"](id); //删除图上的线


            _this5._view.remove(link.viewgroup); //删除点线关系


            var fromid = link.fromid;
            var toid = link.toid; //删除起始点,终止点的电线关系

            if (_this5._pointLinkRelation.has(fromid)) {
              var fromPointLinks = _this5._pointLinkRelation.get(fromid);

              fromPointLinks["delete"](id); //如果点线关系size = 0 则删除点线关系集合

              if (fromPointLinks.size == 0) {
                _this5._pointLinkRelation["delete"](fromid);
              }
            }

            if (_this5._pointLinkRelation.has(toid)) {
              var toPointLinks = _this5._pointLinkRelation.get(toid);

              toPointLinks["delete"](id); //如果点线关系size = 0 则删除点线关系集合

              if (toPointLinks.size == 0) {
                _this5._pointLinkRelation["delete"](toid);
              }
            }
          }
        }); //刷新图

        this._view.refresh();
      }
      /**
       * 基于点id获取唯一点模型数据
       * @param pointid
       * @returns {*}
       */

    }, {
      key: "getPoint",
      value: function getPoint(pointid) {
        if (this._allPoints.has(pointid)) {
          return this._allPoints.get(pointid);
        }

        return null;
      }
      /**
       * 根据linkid获取Link模型数据
       * @param linkid
       */

    }, {
      key: "getLink",
      value: function getLink(linkid) {
        if (this._allLinks.has(linkid)) {
          return this._allLinks.get(linkid);
        }

        return null;
      }
      /**
       * 获取两个点之间的连线
       * @param fromid
       * @param toid
       */

    }, {
      key: "getLinksBetweenTwoPoint",
      value: function getLinksBetweenTwoPoint(fromid, toid) {
        var reLinks = new Set();

        if (fromid === toid) {
          return reLinks;
        }

        var fromLinks = this._pointLinkRelation.get(fromid);

        var toLinks = this._pointLinkRelation.get(toid);

        if (fromLinks == null || toLinks == null) {
          return reLinks;
        }

        var _iterator4 = _createForOfIteratorHelper(fromLinks),
            _step4;

        try {
          for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
            var l = _step4.value;

            if (toLinks.has(l)) {
              reLinks.add(l);
            }
          }
        } catch (err) {
          _iterator4.e(err);
        } finally {
          _iterator4.f();
        }

        return reLinks;
      }
      /**
       * @方法说明：修改线可以适应  单个 link和 一组 link
       * @参数： 线
       * @返回：
       */

    }, {
      key: "getLinksOfPointById",
      value: function getLinksOfPointById(id) {
        var relationLinkIds = this._pointLinkRelation.get(id);

        if (relationLinkIds != null) {
          var relationLinks = [];

          var _iterator5 = _createForOfIteratorHelper(relationLinkIds),
              _step5;

          try {
            for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
              var linkid = _step5.value;

              if (this._allLinks.has(linkid)) {
                relationLinks.push(this._allLinks.get(linkid));
              }
            }
          } catch (err) {
            _iterator5.e(err);
          } finally {
            _iterator5.f();
          }

          return relationLinks;
        } else {
          return [];
        }
      }
      /**
       * @param id
       * @returns {Array}
       * 递归获取一个父亲下的所有的点数据
       */

    }, {
      key: "getPointsByParentId",
      value: function getPointsByParentId(id) {
        var allPoints = [];

        if (this._parentChildRelation.has(id)) {
          var set = this._parentChildRelation.get(id);

          var _iterator6 = _createForOfIteratorHelper(set),
              _step6;

          try {
            for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
              var pointid = _step6.value;
              var point = this.getPoint(pointid);

              if (point != null) {
                allPoints.push(point);

                if (point instanceof Group$1) {
                  var nextAllPoints = this.getPointsByParentId(pointid);
                  allPoints = allPoints.concat(nextAllPoints);
                }
              }
            }
          } catch (err) {
            _iterator6.e(err);
          } finally {
            _iterator6.f();
          }
        }

        return allPoints;
      }
      /**
       * 获取当前所有的点的数据,不包含组数据
       * 返回数组
       */

    }, {
      key: "getAllPoints",
      value: function getAllPoints() {
        var _allPoints = this._allPoints;
        var _allPointArr = [];

        _allPoints.forEach(function (el) {
          if (el instanceof Point) {
            _allPointArr.push(el);
          }
        });

        return _allPointArr;
      }
      /**
       * 返回所有的线对象
       * @returns {Array}
       */

    }, {
      key: "getAllLinks",
      value: function getAllLinks() {
        var _allLinks = this._allLinks;
        var _allLinkArr = [];

        _allLinks.forEach(function (el) {
          if (el instanceof Link) {
            _allLinkArr.push(el);
          }
        });

        return _allLinkArr;
      }
      /**
       * 根据model获取和model相关的点线数据model集合
       */

    }, {
      key: "getIndependentDataByModel",
      value: function getIndependentDataByModel(model) {
        //相关联的点,相关联的线
        var relatedPointIds = new Set();
        var relatedLinkIds = new Set();

        if (model instanceof Link) {
          var link = this.getLink(model.id);
          relatedLinkIds.add(model.id);
          relatedPointIds.add(link.fromid);
          relatedPointIds.add(link.toid);
        }

        if (model instanceof Point) {
          relatedPointIds.add(model.id);
          var relatedLinks = this.getLinksOfPointById(model.id);
          relatedLinks.forEach(function (link) {
            relatedLinkIds.add(link.id);
            relatedPointIds.add(link.fromid);
            relatedPointIds.add(link.toid);
          });
        }

        if (model instanceof Group$1) {
          return [];
        }

        var allModels = [];

        this._allPoints.forEach(function (point, key) {
          if (!relatedPointIds.has(key)) {
            allModels.push(point);
          }
        });

        this._allLinks.forEach(function (link, key) {
          if (!relatedLinkIds.has(key)) {
            allModels.push(link);
          }
        });

        return allModels;
      }
      /**
       * 根据model获取和model相关的点线数据model集合
       */

    }, {
      key: "getIndependentDataByModelIds",
      value: function getIndependentDataByModelIds(ids) {
        //相关联的点,相关联的线
        var relatedPointIds = new Set();
        var relatedLinkIds = new Set();

        if (!(ids instanceof Array)) {
          ids = [ids];
        } //使用箭头函数


        ids.forEach(function (id) {
          relatedPointIds.add(id);
          relatedLinkIds.add(id);
        });
        var allModels = [];

        this._allPoints.forEach(function (point, key) {
          if (!relatedPointIds.has(key)) {
            allModels.push(point);
          }
        });

        this._allLinks.forEach(function (link, key) {
          if (!relatedLinkIds.has(key)) {
            allModels.push(link);
          }
        });

        return allModels;
      }
      /**
           * 根据model获取和model相关的点线数据model集合
           */

    }, {
      key: "getDependentDataByModelIds",
      value: function getDependentDataByModelIds(ids) {
        //相关联的点,相关联的线
        var relatedPointIds = new Set();
        var relatedLinkIds = new Set();

        if (!(ids instanceof Array)) {
          ids = [ids];
        } //使用箭头函数


        ids.forEach(function (id) {
          relatedPointIds.add(id);
          relatedLinkIds.add(id);
        });
        var allModels = [];

        this._allPoints.forEach(function (point, key) {
          if (relatedPointIds.has(key)) {
            allModels.push(point);
          }
        });

        this._allLinks.forEach(function (link, key) {
          if (relatedLinkIds.has(key)) {
            allModels.push(link);
          }
        });

        return allModels;
      }
      /**
       * 获取所有的对象点线
       */

    }, {
      key: "getAllObjects",
      value: function getAllObjects() {
        var allModels = [];

        this._allPoints.forEach(function (v) {
          allModels.push(v);
        });

        this._allLinks.forEach(function (v) {
          allModels.push(v);
        });

        return allModels;
      }
      /**
       * 获取选中的点的数据
       */

    }, {
      key: "getSelectedPoints",
      value: function getSelectedPoints() {
        var _this6 = this;

        var pointArr = [];

        this._selectPoint.forEach(function (id) {
          var point = _this6.getPoint(id);

          pointArr.push(point);
        });

        return pointArr;
      }
      /**
       * 向外提供方法 高亮点
       * @param pointid,
       * 支持一组id
       */

    }, {
      key: "selectPoint",
      value: function selectPoint(pointid) {
        var _this7 = this;

        if (!(pointid instanceof Array)) {
          pointid = [pointid];
        }

        pointid.forEach(function (id) {
          var point = _this7.getPoint(id);

          if (point != null) {
            point.select();
          } else {
            console.warn('select point error:point is null -- pointid=' + id);
          }
        });
      }
      /**
       * 清空选中,如果传递id清空一个,否则清空所有的选中
       * 支持一个id,一组id,不传递id
       * @param pointid
       */

    }, {
      key: "clearSelectPoint",
      value: function clearSelectPoint(pointid) {
        var _this8 = this;

        if (pointid) {
          if (!(pointid instanceof Array)) {
            pointid = [pointid];
          }
        } else {
          pointid = this._selectPoint;
        }

        var pointArr = [];
        pointid.forEach(function (id) {
          var point = _this8.getPoint(id);

          pointArr.push(point);
        });
        pointArr.forEach(function (point) {
          point.select(false);
        });
      }
    }]);

    return UTopoStorage;
  }();

  /**
   * 动画执行者,根据link生成动画功能
   * @param utopo
   * @param config
   * @constructor
   */

  var UTopoAnimator = /*#__PURE__*/function () {
    function UTopoAnimator(utopo, config) {
      _classCallCheck(this, UTopoAnimator);

      this.topo = utopo; //topo对象唯一

      this.time = 3000;
      this.index = 0;
      this.animations = []; //运动小球样式

      this.animationPoint = null;
      this.animationLinks = config.links;
      this.size = 0;
      this.init(config);
    }
    /**
     * 初始化方法
     */


    _createClass(UTopoAnimator, [{
      key: "init",
      value: function init(config) {
        //修改配置
        this.createAnimationDefaultConfig(config); //创建运动小球

        this.animationPoint = this.createAnimationPoint(config.pointConfig); //创建动画

        this.createAnimation(); //记录路径动画个数

        this.size = this.animations.length;
      }
      /**
       * for循环创建动画功能
       */

    }, {
      key: "createAnimation",
      value: function createAnimation() {
        var _this = this;

        var animationPoint = this.animationPoint;
        this.animationLinks.forEach(function (link) {
          var animation = link.animate('shape', true).when(_this.time, {
            percent: 1
          }).during(function (shape) {
            //动画过程中处理关联小球位置
            var pat = getBezierCurvePointAt$2(shape, shape.percent);
            animationPoint.attr('shape', {
              cx: pat.x,
              cy: pat.y
            });
          });
          animation.path = true;

          _this.animations.push(animation);
        });
      }
      /**
       * 创建小球并管理到zrender
       * @param pointConfig
       */

    }, {
      key: "createAnimationPoint",
      value: function createAnimationPoint(pointConfig) {
        var animationPoint = new ZrCircle({
          zlevel: GlobalConfig.level.ANIMATION_LEVEL,
          shape: {
            cx: 0,
            cy: 0,
            r: pointConfig.r ? pointConfig.r : 5
          },
          style: {
            fill: pointConfig.color ? pointConfig.color : 'green'
          }
        });

        this.topo._view.add(animationPoint);

        return animationPoint;
      }
      /**
       * 启动动画效果
       *
       */

    }, {
      key: "start",
      value: function start() {
        var _this2 = this;
        var animation = this.animations[this.index % this.size];
        animation.flag = false;

        if (this.index++ < this.size) {
          animation.during(function (opt, p) {
            if (p != 1 && p >= 0.95 && !animation.flag) {
              animation.flag = true;

              _this2.start();
            }

            if (p == 1 && animation.flag) {
              animation.pause();
            }
          }).start();
        } else {
          if (animation.isPaused()) {
            animation.resume();
          }
        }
      }
    }, {
      key: "createAnimationDefaultConfig",
      value: function createAnimationDefaultConfig(config) {
        this.time = config.time ? config.time : 5000;

        if (isNaN(this.time)) {
          this.time = 3000;
        } else if (this.time < 3000) {
          this.time = 3000;
        }
      }
    }]);

    return UTopoAnimator;
  }();

  function getBezierCurvePointAt$2(shape, t) {
    if (!t) {
      t = 0;
    }

    var x = (1 - t) * (1 - t) * shape.x1 + 2 * t * (1 - t) * shape.cpx1 + t * t * shape.x2;
    var y = (1 - t) * (1 - t) * shape.y1 + 2 * t * (1 - t) * shape.cpy1 + t * t * shape.y2;
    return {
      x: x,
      y: y
    };
  }

  /**
   * utopo animation管理类
   * @author zhangnt
   * @param utopo
   */

  var UTopoAnimation = /*#__PURE__*/function () {
    function UTopoAnimation(utopo) {
      _classCallCheck(this, UTopoAnimation);

      //topo对象唯一
      this.animators = new Map();
      this._topo = utopo; //调用初始化方法

      this.init();
    }
    /**
     * 初始化方法
     */


    _createClass(UTopoAnimation, [{
      key: "init",
      value: function init() {
        console.log('init animation success');
      }
      /**
       * 添加路径动画的方法
       * config{
       *
       * }
       */

    }, {
      key: "addPath",
      value: function addPath(config) {
        this.createDefaultConfig(config); //所有线

        var linkIds = config.links; //起点

        var startPoint = this.calculatePathStartPoint(linkIds, config.point); //循环所有线生成动画对象

        var animationLinks = this.createLinkForPath(startPoint, linkIds); //配置项

        var animatorConfig = {
          time: config.time,
          links: animationLinks,
          pointConfig: {
            color: config.color,
            r: config.r
          }
        };
        var animator = new UTopoAnimator(this._topo, animatorConfig);
        this.animators.set(config.id, animator);
        animator.start();
        return config.id;
      }
    }, {
      key: "getPath",
      value: function getPath(id) {
        return this.animators.get(id);
      }
      /**
       * 生成线动画的默认配置项配置项
       */

    }, {
      key: "createDefaultConfig",
      value: function createDefaultConfig(config) {
        var id = guid();
        config.id = id;

        if (config.time && config.time < 3000) {
          config.time = 3000;
        }
      }
      /**
       * 创建动画路径线数据,从拓扑数据获取线,并生成动画对象
       */

    }, {
      key: "createLinkForPath",
      value: function createLinkForPath(startPoint, linkIds) {
        var _this = this;

        var animationLinks = [];
        linkIds.forEach(function (id) {
          var link = _this._topo._storage.getLink(id); //判断在当前拓扑中线路径是否绘图翻转


          var reverseFlag = false;

          if (link.fromid == startPoint) {
            startPoint = link.toid;
          } else if (link.toid == startPoint) {
            startPoint = link.fromid;
            reverseFlag = true;
          }

          var opt = {
            reverse: reverseFlag
          };
          var animationLink = link.addPathSimpleAnimation(opt);
          animationLinks.push(animationLink);
        });
        return animationLinks;
      }
      /**
       * 计算路径动画起始点
       */

    }, {
      key: "calculatePathStartPoint",
      value: function calculatePathStartPoint(linkIds, point) {
        var startPoint = point; //没有起点则自动按照点线计算

        if (startPoint == null || startPoint == undefined) {
          try {
            var link1 = this._topo._storage.getLink(linkIds[0]);

            var link2 = this._topo._storage.getLink(linkIds[1]);

            var fromid1 = link1.fromid;
            var toid1 = link1.toid;
            var fromid2 = link2.fromid;
            var toid2 = link2.toid;

            if (fromid1 == fromid2 || fromid1 == toid2) {
              startPoint = toid1;
            } else if (toid1 == fromid2 || toid1 == toid2) {
              startPoint = fromid1;
            } else {
              console.log("路径配置有误,请检查");
              startPoint = null;
            }
          } catch (e) {
            console.error('未知错误', e);
          }
        }

        return startPoint;
      }
    }]);

    return UTopoAnimation;
  }();

  /**
   *
   * zrender封装的topo入口程序类
   * 提供对外方法
   * @author zhangnt
   *
   */

  /**
   * utopo实例集合
   * @type {{}}
   */

  var instances = {}; // utopo实例map索引

  /**
   * 版本号
   * @type {string}
   */

  var version = '2.3.1';
  /**
   * 初始化方法
   */

  function init$1(opt) {
    var utopo = new UTopo(guid(), opt);
    instances[utopo.id] = utopo;
    return utopo;
  }
  /**
   *
   * @param utopo
   * @returns {dispose}
   */

  function dispose(utopo) {
    if (utopo) {
      utopo.dispose();
    } else {
      for (var key in instances) {
        if (instances.hasOwnProperty(key)) {
          instances[key].dispose();
        }
      }

      instances = {};
    }

    return this;
  }
  function getInstance(id) {
    return instances[id];
  }
  function delInstance$1(id) {
    delete instances[id];
  }
  /**
   * H5版本topo入口程序
   * @constructor
   */

  var UTopo = /*#__PURE__*/function () {
    function UTopo(id, opt) {
      _classCallCheck(this, UTopo);

      this.id = id; //获取配置的拓扑图的名称,可用于展示

      this.name = opt.name ? opt.name : null;
      this._config = opt;
      this._view = null; //数据存储层

      this._storage = null;
      this._animation = null; //调用初始化方法生成对象数据

      this.init();
    }
    /**
     * 初始化方法
     */


    _createClass(UTopo, [{
      key: "init",
      value: function init() {
        this.initConfig();
        this._view = new UTopoView(this);
        this._storage = new UTopoStorage(this);
        this._animation = new UTopoAnimation(this);
      }
    }, {
      key: "initConfig",
      value: function initConfig() {
        //配置默认配置数据
        var defaultConfig = {
          //是否缩放
          roam: false,
          //焦点默认开启
          focus: true,
          //拖拽是否开启,默认开启
          draggable: true,
          //默认线动画不开启
          animation: false
        }; //合并属性

        merge(defaultConfig, this._config, true);
        this._config = defaultConfig;
      }
      /**
       * 获取实例唯一标识
       * @return {string}
       */

    }, {
      key: "getId",
      value: function getId() {
        return this.id;
      }
      /**
       * 添加点元素
       * @param {} el 
       */

    }, {
      key: "addPoint",
      value: function addPoint(el) {
        this._storage.addPoint(el);
      }
      /**
       * 添加线元素
       */

    }, {
      key: "addLink",
      value: function addLink(el) {
        this._storage.addLink(el);
      }
      /**
      * 删除点元素,包括组对象,入参为id 可以为数组,可以为单个id
      * @param {} el 
      */

    }, {
      key: "delPoint",
      value: function delPoint(ids) {
        this._storage.delPoint(ids);
      }
      /**
       * 删除线元素,入参为线id,可以为数组,可以为单个id
       */

    }, {
      key: "delLink",
      value: function delLink(ids) {
        this._storage.delLink(ids);
      }
      /**
       * 重新计算大小
       */

    }, {
      key: "resize",
      value: function resize() {
        this._view.resize();
      }
      /**
       * Get container width
       */

    }, {
      key: "getWidth",
      value: function getWidth() {
        return this._view.getWidth();
      }
      /**
       * Get container height
       */

    }, {
      key: "getHeight",
      value: function getHeight() {
        return this._view.getHeight();
      }
      /**
       * 刷新显示
       */

    }, {
      key: "refresh",
      value: function refresh() {
        this._view.refresh();
      }
      /**
       * 选中点
       */

    }, {
      key: "selectPoint",
      value: function selectPoint(id) {
        this._storage.selectPoint(id);
      }
      /**
       * 清空选择点
       */

    }, {
      key: "clearSelectPoint",
      value: function clearSelectPoint(id) {
        this._storage.clearSelectPoint(id);
      }
      /**
       * 获取当前缩放比例
       */

    }, {
      key: "getScale",
      value: function getScale() {
        return this._view.getScale();
      }
      /**
       * 清空对象数据,重新添加
       * 调用zrender.dispose()删除源zrender对象,重新创建
       * 调用init方法重新创建
       */

    }, {
      key: "clear",
      value: function clear() {
        //清空绘图
        this._view.dispose(); //清空数据


        this._view = null;
        this._storage = null; //重新init

        this.init();
      }
      /**
       * 删除对象,仅仅删除utopo对象实例中的对象,
       * 并删除对象中的view,storage对象数据。对象的引用依然在
       */

    }, {
      key: "dispose",
      value: function dispose() {
        this._view.dispose();

        this._view = null;
        this._storage = null;
        delInstance$1(this.id);
      }
      /**
       * 返回数组对象
       * @returns {Array}
       */

    }, {
      key: "getAllPoints",
      value: function getAllPoints() {
        return this._storage.getAllPoints();
      }
    }, {
      key: "getAllLinks",
      value: function getAllLinks() {
        return this._storage.getAllLinks();
      }
    }, {
      key: "addContextMenu",
      value: function addContextMenu(menuConfig) {
        this._view.addContextMenu(menuConfig);
      }
    }, {
      key: "addTooltip",
      value: function addTooltip(tooltipConfig) {
        this._view.addTooltip(tooltipConfig);
      }
    }, {
      key: "addLegend",
      value: function addLegend(legendConfig) {
        this._view.addLegend(legendConfig);
      }
    }, {
      key: "removeLegend",
      value: function removeLegend(name) {
        this._view.removeLegend(name);
      }
    }, {
      key: "toImage",
      value: function toImage(filename) {
        var c = this._view._zrender.painter.getRenderedCanvas();

        var image = c.toDataURL('image/jpg');
        var save_link = document.createElement('a');
        save_link.href = image;

        if (filename) {
          save_link.download = filename;
        } else {
          save_link.download = (this.name ? this.name : 'topo_') + new Date().getTime() + '.png';
        } //生成事件,自动下发执行


        var event = document.createEvent('MouseEvents');
        event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); //下发

        save_link.dispatchEvent(event);
      }
      /**
       * 导出图片:
       * 背景图片，延迟时间
       */

    }, {
      key: "toImage1",
      value: function toImage1(backgroundImg, settimeout) {
        var _this = this;

        var timeout = 800;

        if (settimeout && !isNaN(settimeout)) {
          timeout = settimeout;
        }

        var zrBgImage = null;

        if (backgroundImg) {
          var img = new Image(); //新建一个图片对象//

          img.src = backgroundImg; //将图片的src属性赋值给新建的图片对象的src

          img.onload = function () {
            var imgWidth = img.width;
            var imgHeight = img.height;

            var zw = _this._view._zrender.getWidth();

            var zh = _this._view._zrender.getHeight();

            var x = 0,
                y = 0,
                w = 0,
                h = 0; //按照宽画图

            if (imgWidth / imgHeight > zw / zh) {
              x = 0;
              y = (zh - zw * imgHeight / imgWidth) / 2;
              w = zw;
              h = zw * imgHeight / imgWidth;
            } else {
              //按照高画
              x = (zw - zh * imgWidth / imgHeight) / 2;
              y = 0;
              w = zh * imgWidth / imgHeight;
              h = zh;
            }

            zrBgImage = new ZImage({
              style: {
                x: x,
                y: y,
                width: w,
                height: h,
                image: backgroundImg
              }
            });

            _this._view._zrender.add(zrBgImage);
          };
        }

        setTimeout(function () {
          var c = _this._view._zrender.painter.getRenderedCanvas();

          var image = c.toDataURL('image/jpg');
          var save_link = document.createElement('a');
          save_link.href = image;
          save_link.download = (_this.name ? _this.name : 'topo_') + new Date().getTime() + '.png'; //生成事件,自动下发执行

          var event = document.createEvent('MouseEvents');
          event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); //删除添加的背景图片

          if (zrBgImage != null) {
            console.log('remove bg' + zrBgImage.id);

            _this._view._zrender.remove(zrBgImage);
          } //下发


          save_link.dispatchEvent(event);
        }, timeout);
      }
      /**
       * 导出图片:
       * 背景图片，延迟时间
       */

    }, {
      key: "toImage2",
      value: function toImage2(backgroundImg, settimeout) {
        var _this2 = this;

        var timeout = 800;

        if (settimeout && !isNaN(settimeout)) {
          timeout = settimeout;
        }

        var zrBgImage = null;

        if (backgroundImg) {
          var img = new Image(); //新建一个图片对象//

          img.src = backgroundImg; //将图片的src属性赋值给新建的图片对象的src

          img.onload = function () {
            var imgWidth = img.width;
            var imgHeight = img.height;

            var zw = _this2._view._zrender.getWidth();

            var zh = _this2._view._zrender.getHeight();

            var x = 0,
                y = 0,
                w = 0,
                h = 0; //按照宽画图

            if (imgWidth / imgHeight > zw / zh) {
              x = 0;
              y = (zh - zw * imgHeight / imgWidth) / 2;
              w = zw;
              h = zw * imgHeight / imgWidth;
            } else {
              //按照高画
              x = (zw - zh * imgWidth / imgHeight) / 2;
              y = 0;
              w = zh * imgWidth / imgHeight;
              h = zh;
            }

            zrBgImage = new ZImage({
              style: {
                x: x,
                y: y,
                width: w,
                height: h,
                image: backgroundImg
              }
            });

            _this2._view._zrender.add(zrBgImage);
          };
        }

        setTimeout(function () {
          _this2._view._zrender.refreshImmediately();

          var c = _this2._view._zrender.painter.getRenderedCanvas();

          var image = c.toDataURL('image/jpg');
          var save_link = document.createElement('a');
          save_link.href = image;
          save_link.download = (_this2.name ? _this2.name : 'topo_') + new Date().getTime() + '.png'; //生成事件,自动下发执行

          var event = document.createEvent('MouseEvents');
          event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); //删除添加的背景图片

          if (zrBgImage != null) {
            console.log('remove bg' + zrBgImage.id);

            _this2._view._zrender.remove(zrBgImage);
          } //下发


          save_link.dispatchEvent(event);
        }, timeout);
      }
      /**
       * 将所有点排列成圆的代码例子
       */

    }, {
      key: "autoCircleRank",
      value: function autoCircleRank(r) {
        var width = this.getWidth();
        var height = this.getHeight();
        var x0 = width / 2;
        var y0 = height / 2;

        if (!r) {
          r = height / 3;
        }

        var i = 0;
        var points = this.getAllPoints();
        var size = points.length;
        points.forEach(function (point) {
          var x = x0 + Math.cos(360 / size * i * Math.PI / 180) * r;
          var y = y0 + Math.sin(360 / size * i * Math.PI / 180) * r;
          point.moveTo(x, y);
          i++;
        });
      }
      /**
       * 拓扑图居中显示
       */

    }, {
      key: "autoCenter",
      value: function autoCenter() {
        this._view.autoCenter();
      }
    }, {
      key: "addPathAnimation",
      value: function addPathAnimation(config) {
        this._animation.addPath(config);
      }
    }, {
      key: "autoAdapt",
      value: function autoAdapt() {
        this._view.autoAdapt();
      }
    }, {
      key: "focus",
      value: function focus(ids) {
        this._view.focus(ids);
      }
    }, {
      key: "getScale",
      value: function getScale() {
        return this._view.getScale();
      }
      /**
       * 针对当前缩放大小增加减小
       */

    }, {
      key: "scaleAdd",
      value: function scaleAdd(num) {
        this._view.scaleAdd(num);
      }
      /**
       * 针对当前缩放大小，进行指定缩放大小缓缓
       */

    }, {
      key: "scaleTo",
      value: function scaleTo(scale) {
        this._view.scaleTo(scale);
      }
    }]);

    return UTopo;
  }();

  var Utopo = /*#__PURE__*/Object.freeze({
    __proto__: null,
    version: version,
    init: init$1,
    dispose: dispose,
    getInstance: getInstance,
    delInstance: delInstance$1
  });



  var Topo = /*#__PURE__*/Object.freeze({
    __proto__: null,
    Point: Point,
    Link: Link,
    Group: Group$1
  });

  var ucharts_all = _objectSpread2({}, Utopo, {}, Topo);

  return ucharts_all;

})));
